Repository: go-kratos/kratos Branch: main Commit: e36259de52a8 Files: 461 Total size: 2.0 MB Directory structure: gitextract_rshunaq6/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ ├── config.yml │ │ ├── feature-request.md │ │ ├── proposal.md │ │ └── question.md │ ├── dependabot.yml │ ├── pull_request_template.md │ ├── semantic.yml │ ├── stable.yml │ └── workflows/ │ ├── codeql-analysis.yml │ ├── comment-check.yml │ ├── gitee-sync.yml │ ├── go.yml │ ├── issue-translator.yml │ └── lint.yml ├── .gitignore ├── .golangci.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── README_zh.md ├── ROADMAP.md ├── SECURITY.md ├── api/ │ ├── README.md │ └── metadata/ │ ├── metadata.pb.go │ ├── metadata.proto │ ├── metadata_grpc.pb.go │ ├── metadata_http.pb.go │ └── server.go ├── app.go ├── app_test.go ├── cmd/ │ ├── kratos/ │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── base/ │ │ │ │ ├── install.go │ │ │ │ ├── mod.go │ │ │ │ ├── mod_test.go │ │ │ │ ├── path.go │ │ │ │ ├── repo.go │ │ │ │ ├── repo_test.go │ │ │ │ ├── vcs_url.go │ │ │ │ └── vcs_url_test.go │ │ │ ├── change/ │ │ │ │ ├── change.go │ │ │ │ ├── get.go │ │ │ │ └── get_test.go │ │ │ ├── project/ │ │ │ │ ├── add.go │ │ │ │ ├── new.go │ │ │ │ ├── project.go │ │ │ │ ├── project_linux_test.go │ │ │ │ ├── project_test.go │ │ │ │ └── project_windows_test.go │ │ │ ├── proto/ │ │ │ │ ├── add/ │ │ │ │ │ ├── add.go │ │ │ │ │ ├── add_test.go │ │ │ │ │ ├── proto.go │ │ │ │ │ └── template.go │ │ │ │ ├── client/ │ │ │ │ │ └── client.go │ │ │ │ ├── proto.go │ │ │ │ └── server/ │ │ │ │ ├── server.go │ │ │ │ ├── server_test.go │ │ │ │ └── template.go │ │ │ ├── run/ │ │ │ │ └── run.go │ │ │ └── upgrade/ │ │ │ └── upgrade.go │ │ ├── main.go │ │ └── version.go │ ├── protoc-gen-go-errors/ │ │ ├── buf.gen.yaml │ │ ├── buf.yaml │ │ ├── errors/ │ │ │ ├── errors.pb.go │ │ │ └── errors.proto │ │ ├── errors.go │ │ ├── errorsTemplate.tpl │ │ ├── errors_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── main.go │ │ ├── template.go │ │ └── version.go │ └── protoc-gen-go-http/ │ ├── go.mod │ ├── go.sum │ ├── http.go │ ├── httpTemplate.tpl │ ├── http_test.go │ ├── main.go │ ├── template.go │ └── version.go ├── codecov.yml ├── config/ │ ├── README.md │ ├── config.go │ ├── config_test.go │ ├── env/ │ │ ├── env.go │ │ ├── env_test.go │ │ ├── watcher.go │ │ └── watcher_test.go │ ├── file/ │ │ ├── file.go │ │ ├── file_test.go │ │ ├── format.go │ │ ├── format_test.go │ │ └── watcher.go │ ├── options.go │ ├── options_test.go │ ├── reader.go │ ├── reader_test.go │ ├── source.go │ ├── value.go │ └── value_test.go ├── contrib/ │ ├── config/ │ │ ├── apollo/ │ │ │ ├── README.md │ │ │ ├── apollo.go │ │ │ ├── apollo_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── parser.go │ │ │ ├── watcher.go │ │ │ └── watcher_test.go │ │ ├── consul/ │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── watcher.go │ │ ├── etcd/ │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── watcher.go │ │ ├── kubernetes/ │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── watcher.go │ │ │ └── watcher_test.go │ │ ├── nacos/ │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── watcher.go │ │ └── polaris/ │ │ ├── README.md │ │ ├── config.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── polaris.yaml │ │ └── watcher.go │ ├── encoding/ │ │ └── msgpack/ │ │ ├── go.mod │ │ ├── go.sum │ │ ├── msgpack.go │ │ └── msgpack_test.go │ ├── errortracker/ │ │ └── sentry/ │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── sentry.go │ │ └── sentry_test.go │ ├── log/ │ │ ├── aliyun/ │ │ │ ├── aliyun.go │ │ │ ├── aliyun_test.go │ │ │ ├── go.mod │ │ │ └── go.sum │ │ ├── fluent/ │ │ │ ├── fluent.go │ │ │ ├── fluent_test.go │ │ │ ├── go.mod │ │ │ └── go.sum │ │ ├── logrus/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── logrus.go │ │ │ └── logrus_test.go │ │ ├── tencent/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── tencent.go │ │ │ └── tencent_test.go │ │ ├── zap/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── zap.go │ │ │ └── zap_test.go │ │ └── zerolog/ │ │ ├── go.mod │ │ ├── go.sum │ │ ├── zerolog.go │ │ └── zerolog_test.go │ ├── middleware/ │ │ └── validate/ │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ └── testdata/ │ │ │ ├── generate.go │ │ │ ├── test.pb.go │ │ │ ├── test.pb.validate.go │ │ │ └── test.proto │ │ ├── validate.go │ │ └── validate_test.go │ ├── opensergo/ │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── opensergo.go │ │ └── opensergo_test.go │ ├── polaris/ │ │ ├── config.go │ │ ├── config_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── limiter.go │ │ ├── polaris.go │ │ ├── ratelimit.go │ │ ├── registry.go │ │ ├── registry_test.go │ │ ├── router.go │ │ └── router_test.go │ ├── registry/ │ │ ├── consul/ │ │ │ ├── client.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── registry.go │ │ │ ├── registry_test.go │ │ │ ├── service.go │ │ │ └── watcher.go │ │ ├── discovery/ │ │ │ ├── README.md │ │ │ ├── discovery.go │ │ │ ├── discovery_helper.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── impl_discover.go │ │ │ └── impl_registrar.go │ │ ├── etcd/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── registry.go │ │ │ ├── registry_test.go │ │ │ ├── service.go │ │ │ └── watcher.go │ │ ├── eureka/ │ │ │ ├── client.go │ │ │ ├── eureka.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── register.go │ │ │ ├── register_test.go │ │ │ └── watcher.go │ │ ├── kubernetes/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── registry.go │ │ │ └── registry_test.go │ │ ├── nacos/ │ │ │ ├── README.md │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── registry.go │ │ │ ├── registry_test.go │ │ │ └── watcher.go │ │ ├── polaris/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── registry.go │ │ ├── servicecomb/ │ │ │ ├── README.md │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── registry.go │ │ │ ├── registry_test.go │ │ │ └── watcher.go │ │ └── zookeeper/ │ │ ├── go.mod │ │ ├── go.sum │ │ ├── register.go │ │ ├── register_test.go │ │ ├── service.go │ │ └── watcher.go │ └── transport/ │ └── mcp/ │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── server.go │ └── server_test.go ├── docs/ │ ├── README.md │ └── design/ │ └── kratos-v2.md ├── encoding/ │ ├── README.md │ ├── encoding.go │ ├── encoding_test.go │ ├── form/ │ │ ├── form.go │ │ ├── form_test.go │ │ ├── proto_decode.go │ │ ├── proto_decode_test.go │ │ ├── proto_encode.go │ │ ├── proto_encode_test.go │ │ ├── well_known_types.go │ │ └── well_known_types_test.go │ ├── json/ │ │ ├── json.go │ │ └── json_test.go │ ├── proto/ │ │ ├── proto.go │ │ └── proto_test.go │ ├── xml/ │ │ ├── xml.go │ │ └── xml_test.go │ └── yaml/ │ ├── yaml.go │ └── yaml_test.go ├── errors/ │ ├── errors.go │ ├── errors.pb.go │ ├── errors.proto │ ├── errors_test.go │ ├── types.go │ ├── types_test.go │ ├── wrap.go │ └── wrap_test.go ├── go.mod ├── go.sum ├── hack/ │ ├── .lintcheck_failures │ ├── .test_ignored_files │ ├── resolve-modules.sh │ ├── tools.sh │ └── util.sh ├── internal/ │ ├── README.md │ ├── context/ │ │ ├── context.go │ │ └── context_test.go │ ├── endpoint/ │ │ ├── endpoint.go │ │ └── endpoint_test.go │ ├── group/ │ │ ├── example_test.go │ │ ├── group.go │ │ └── group_test.go │ ├── host/ │ │ ├── host.go │ │ └── host_test.go │ ├── httputil/ │ │ ├── http.go │ │ └── http_test.go │ ├── matcher/ │ │ ├── middleware.go │ │ └── middleware_test.go │ └── testdata/ │ ├── binding/ │ │ ├── generate.go │ │ ├── test.pb.go │ │ └── test.proto │ ├── complex/ │ │ ├── complex.pb.go │ │ ├── complex.proto │ │ └── generate.go │ ├── encoding/ │ │ ├── test.pb.go │ │ └── test.proto │ └── helloworld/ │ ├── generate.go │ ├── helloworld.pb.go │ ├── helloworld.proto │ ├── helloworld_grpc.pb.go │ └── helloworld_http.pb.go ├── log/ │ ├── README.md │ ├── filter.go │ ├── filter_test.go │ ├── global.go │ ├── global_test.go │ ├── helper.go │ ├── helper_test.go │ ├── helper_writer.go │ ├── helper_writer_test.go │ ├── level.go │ ├── level_test.go │ ├── log.go │ ├── log_test.go │ ├── std.go │ ├── std_test.go │ ├── value.go │ └── value_test.go ├── metadata/ │ ├── metadata.go │ └── metadata_test.go ├── middleware/ │ ├── auth/ │ │ └── jwt/ │ │ ├── jwt.go │ │ └── jwt_test.go │ ├── circuitbreaker/ │ │ ├── circuitbreaker.go │ │ └── circuitbreaker_test.go │ ├── logging/ │ │ ├── logging.go │ │ └── logging_test.go │ ├── metadata/ │ │ ├── metadata.go │ │ └── metadata_test.go │ ├── metrics/ │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ └── otel.go │ ├── middleware.go │ ├── middleware_test.go │ ├── ratelimit/ │ │ ├── ratelimit.go │ │ └── ratelimit_test.go │ ├── recovery/ │ │ ├── recovery.go │ │ └── recovery_test.go │ ├── selector/ │ │ ├── selector.go │ │ └── selector_test.go │ ├── tracing/ │ │ ├── metadata.go │ │ ├── metadata_test.go │ │ ├── span.go │ │ ├── span_test.go │ │ ├── statshandler.go │ │ ├── statshandler_test.go │ │ ├── tracer.go │ │ ├── tracer_test.go │ │ ├── tracing.go │ │ └── tracing_test.go │ └── validate/ │ ├── validate.go │ └── validate_test.go ├── options.go ├── options_test.go ├── registry/ │ ├── README.md │ └── registry.go ├── selector/ │ ├── balancer.go │ ├── default_node.go │ ├── default_selector.go │ ├── filter/ │ │ ├── version.go │ │ └── version_test.go │ ├── filter.go │ ├── global.go │ ├── node/ │ │ ├── direct/ │ │ │ ├── direct.go │ │ │ └── direct_test.go │ │ └── ewma/ │ │ ├── node.go │ │ └── node_test.go │ ├── options.go │ ├── p2c/ │ │ ├── p2c.go │ │ └── p2c_test.go │ ├── peer.go │ ├── peer_test.go │ ├── random/ │ │ ├── random.go │ │ └── random_test.go │ ├── selector.go │ ├── selector_test.go │ └── wrr/ │ ├── wrr.go │ └── wrr_test.go ├── third_party/ │ ├── README.md │ ├── buf/ │ │ └── validate/ │ │ ├── README.md │ │ └── validate.proto │ ├── buf.yaml │ ├── errors/ │ │ └── errors.proto │ ├── google/ │ │ ├── api/ │ │ │ ├── annotations.proto │ │ │ ├── client.proto │ │ │ ├── field_behavior.proto │ │ │ ├── http.proto │ │ │ └── httpbody.proto │ │ └── protobuf/ │ │ └── descriptor.proto │ └── validate/ │ ├── README.md │ └── validate.proto ├── transport/ │ ├── grpc/ │ │ ├── balancer.go │ │ ├── balancer_test.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── codec.go │ │ ├── codec_test.go │ │ ├── interceptor.go │ │ ├── resolver/ │ │ │ ├── direct/ │ │ │ │ ├── builder.go │ │ │ │ ├── builder_test.go │ │ │ │ ├── resolver.go │ │ │ │ └── resolver_test.go │ │ │ └── discovery/ │ │ │ ├── builder.go │ │ │ ├── builder_test.go │ │ │ ├── resolver.go │ │ │ └── resolver_test.go │ │ ├── server.go │ │ ├── server_test.go │ │ ├── transport.go │ │ └── transport_test.go │ ├── http/ │ │ ├── binding/ │ │ │ ├── bind.go │ │ │ ├── bind_test.go │ │ │ ├── encode.go │ │ │ └── encode_test.go │ │ ├── calloption.go │ │ ├── calloption_test.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── codec.go │ │ ├── codec_go1.20.go │ │ ├── codec_test.go │ │ ├── context.go │ │ ├── context_test.go │ │ ├── filter.go │ │ ├── pprof/ │ │ │ └── pprof.go │ │ ├── redirect.go │ │ ├── redirect_test.go │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ ├── router.go │ │ ├── router_test.go │ │ ├── server.go │ │ ├── server_test.go │ │ ├── status/ │ │ │ ├── status.go │ │ │ └── status_test.go │ │ ├── transport.go │ │ └── transport_test.go │ ├── transport.go │ └── transport_test.go └── version.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ open_collective: go-kratos github: [go-kratos] ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: "\U0001F41B Bug Report" about: Report something that's broken. Please ensure your kratos version is still supported. title: '' labels: bug assignees: '' --- #### 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: ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Documentation issue url: https://go-kratos.dev/docs/ about: For documentation issues, open a pull request at the go-kratos/go-kratos.dev repository ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- 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. ### What problem is the feature used to solve? ### Requirements description of the feature ### References ================================================ FILE: .github/ISSUE_TEMPLATE/proposal.md ================================================ --- 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. ### Proposal description ### Implementation mode ### Usage demonstration ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- 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. ================================================ FILE: .github/dependabot.yml ================================================ --- version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" ================================================ FILE: .github/pull_request_template.md ================================================ #### Description (what this PR does / why we need it): #### Which issue(s) this PR fixes (resolves / be part of): #### Other special notes for the reviewers: ================================================ FILE: .github/semantic.yml ================================================ 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 ================================================ FILE: .github/stable.yml ================================================ 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 ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: [ main, v1.0.x ] jobs: analyze: name: Analyze runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: go - name: CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/comment-check.yml ================================================ name: Non-English Comments Check on: pull_request_target: types: [opened, synchronize, reopened] branches: - main # workflow_dispatch: jobs: non-english-comments-check: runs-on: ubuntu-latest permissions: contents: read pull-requests: write env: # Directories to be excluded EXCLUDE_DIRS: ".git docs tests scripts assets node_modules build" # Files to be excluded EXCLUDE_FILES: ".md .txt .html .css .min.js .mdx" steps: - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} fetch-depth: 0 # - name: Search for Non-English comments in the entire repository # run: | # set -e # # Define the regex pattern to match Chinese characters # pattern='[\p{Han}]' # # Use find to get all files in the repository # all_files=$(find . -type f) # # Loop over each file in the repository # for file in $all_files; do # # Skip files in excluded directories # skip_file=false # for dir in ${EXCLUDE_DIRS}; do # if [[ "$file" == ./$dir/* ]]; then # skip_file=true # break # fi # done # # Skip files matching excluded patterns # for file_pattern in ${EXCLUDE_FILES}; do # if [[ "$file" == *$file_pattern ]]; then # skip_file=true # break # fi # done # # If the file matches any exclude pattern, skip it # if [ "$skip_file" = true ]; then # continue # fi # # Use grep to find all comments containing Non-English characters in filtered files # grep_output=$(grep -PnH "$pattern" "$file" || true) # if [ -n "$grep_output" ]; then # # Insert a tab after the line number, keeping the colon between the file path and line number # formatted_output=$(echo "$grep_output" | sed 's/^\(.*:[0-9]\+\):/\1\t/') # echo "$formatted_output" >> non_english_comments.txt # Save to file # fi # done - name: Search for Non-English comments in PR diff files run: | set -e # Define the regex pattern to match Chinese characters pattern='[\p{Han}]' # Get the list of files changed in this PR compared to the base branch changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}) # Loop over each changed file for file in $changed_files; do # Skip files in excluded directories skip_file=false for dir in ${EXCLUDE_DIRS}; do if [[ "$file" == ./$dir/* ]]; then skip_file=true break fi done # Skip files matching excluded patterns for file_pattern in ${EXCLUDE_FILES}; do if [[ "$file" == *$file_pattern ]]; then skip_file=true break fi done # If the file matches any exclude pattern, skip it if [ "$skip_file" = true ]; then continue fi # Use grep to find all comments containing Non-English characters in filtered files grep_output=$(grep -PnH "$pattern" "$file" || true) if [ -n "$grep_output" ]; then # Insert a tab after the line number, keeping the colon between the file path and line number formatted_output=$(echo "$grep_output" | sed 's/^\(.*:[0-9]\+\):/\1\t/') echo "$formatted_output" >> non_english_comments.txt # Save to file fi done - name: Store non-English comments in ENV run: | # Store the entire content of non_english_comments.txt into an environment variable if [ -f non_english_comments.txt ]; then NON_ENGLISH_COMMENTS=$(cat non_english_comments.txt) echo "NON_ENGLISH_COMMENTS<> $GITHUB_ENV echo "$NON_ENGLISH_COMMENTS" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV fi - name: Output non-English comments if found run: | if [ -s non_english_comments.txt ]; then echo "Non-English comments found in the following locations:" cat non_english_comments.txt exit 1 # terminate the workflow else echo "No Non-English comments found." fi - name: Find Comment if: failure() && github.event_name != 'workflow_dispatch' uses: peter-evans/find-comment@v4.0.0 id: fc with: issue-number: ${{ github.event.pull_request.number }} comment-author: "kratos-ci-bot" body-includes: Non-English comments were found in the following locations - name: Comment on PR if errors found if: failure() && github.event_name != 'workflow_dispatch' # This step runs only if the previous step fails uses: peter-evans/create-or-update-comment@v5.0.0 with: token: ${{ secrets.BOT_GITHUB_TOKEN }} issue-number: ${{ github.event.pull_request.number }} comment-id: ${{ steps.fc.outputs.comment-id }} edit-mode: replace body: | ⚠️ Non-English comments were found in the following locations: ``` ${{ env.NON_ENGLISH_COMMENTS }} ``` ================================================ FILE: .github/workflows/gitee-sync.yml ================================================ 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@v6 - name: Mirror Github to Gitee uses: Yikun/hub-mirror-action@v1.5 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" ================================================ FILE: .github/workflows/go.yml ================================================ name: Go on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: build: strategy: matrix: go: [1.22.x, 1.23.x, 1.24.x, 1.25.x] name: build & test runs-on: ubuntu-latest services: etcd: image: gcr.io/etcd-development/etcd:v3.5.0 ports: - 2379:2379 env: ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 consul: image: hashicorp/consul:1.20 ports: - 8500:8500 nacos: image: nacos/nacos-server:v2.1.0 env: MODE: standalone ports: - "8848:8848" - "9848:9848" polaris: image: polarismesh/polaris-standalone:latest ports: - 8090:8090 - 8091:8091 - 8093:8093 steps: - uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - name: Setup Environment run: | echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV echo "$(go env GOPATH)/bin" >> $GITHUB_PATH - name: Module cache uses: actions/cache@v5 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go - name: Build run: go build ./... env: GOTOOLCHAIN: auto - name: Test run: make test-coverage env: GOTOOLCHAIN: auto - name: Upload coverage to Codecov run: bash <(curl -s https://codecov.io/bash) - name: Kratos run: | cd cmd/kratos go build ./... env: GOTOOLCHAIN: auto - name: HTTP run: | cd cmd/protoc-gen-go-http go build ./... go test ./... env: GOTOOLCHAIN: auto ================================================ FILE: .github/workflows/issue-translator.yml ================================================ name: 'issue-translator' on: issue_comment: types: [created] issues: types: [opened] jobs: build: runs-on: ubuntu-latest steps: - uses: usthe/issues-translate-action@v2.7 with: IS_MODIFY_TITLE: true CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿 BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} ================================================ FILE: .github/workflows/lint.yml ================================================ 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@v6 - 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@v6 - name: Lint uses: golangci/golangci-lint-action@v7 with: version: v2.0 working-directory: ${{ matrix.workdir }} skip-pkg-cache: true ================================================ FILE: .gitignore ================================================ # Reference https://github.com/github/gitignore/blob/master/Go.gitignore # Binaries for programs and plugins *.exe *.exe~ *.dll *.dylib # 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 Thumbs.db .DS_Store .cache # project *.cert *.key *.log bin/ # Develop tools .vscode/ .idea/ *.swp ================================================ FILE: .golangci.yml ================================================ version: "2" run: modules-download-mode: readonly linters: default: none enable: - bodyclose - dogsled - durationcheck - errcheck - goconst - gocyclo - govet - ineffassign - lll - misspell - mnd - prealloc - revive - staticcheck - unconvert - unused - wastedassign - whitespace settings: gocyclo: min-complexity: 50 govet: enable: - shadow lll: line-length: 160 misspell: locale: US mnd: checks: - case - condition - return whitespace: multi-func: true exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - goconst path: (.+)_test\.go paths: - third_party$ - builtin$ - examples$ formatters: enable: - gofmt - gofumpt - goimports settings: goimports: local-prefixes: - github.com/go-kratos exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # 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. ================================================ FILE: CONTRIBUTING.md ================================================ 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 ``` [optional scope]: [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 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 go-kratos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ user := $(shell whoami) rev := $(shell git rev-parse --short HEAD) os := $(shell uname) # GOBIN > GOPATH > INSTALLDIR # Mac OS X ifeq ($(os),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 ifneq ($(findstring MINGW,$(shell uname -s)),) GOBIN := $(shell echo "$(GOBIN)" | sed 's|\\|/|g' | cut -d';' -f1 | sed 's|^\([A-Za-z]\):|/\1|') GOPATH := $(shell echo "$(GOPATH)" | sed 's|\\|/|g' | cut -d';' -f1 | sed 's|^\([A-Za-z]\):|/\1|') 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 ================================================ FILE: README.md ================================================

Build Status GoDoc DeepWiki codeCov Go Report Card License Awesome Go Discord

go-kratos%2Fkratos | Trendshift Go Kratos - A Go framework for microservices. | Product Hunt

##### Translate to: [简体中文](README_zh.md) ## About Kratos > The name is inspired by the Greek-mythology-based game "God of War". It tells the adventures of Kratos becoming a god of war from a mortal and launching a god-killing slaughter. Kratos is a microservice-oriented governance framework implemented by golang, which offers convenient capabilities to help you quickly build a bulletproof application from scratch, such as: - The [communication protocol](https://go-kratos.dev/docs/component/api) is based on the HTTP/gRPC through the definition of Protobuf. - Abstract [transport](https://go-kratos.dev/docs/component/transport/overview) layer support: [HTTP](https://go-kratos.dev/docs/component/transport/http) / [gRPC](https://go-kratos.dev/docs/component/transport/grpc). - Powerful [middleware](https://go-kratos.dev/docs/component/middleware/overview) design, support: [Tracing (OpenTelemetry)](https://go-kratos.dev/docs/component/middleware/tracing), [Metrics (Prometheus is default)](https://go-kratos.dev/docs/component/middleware/metrics), [Recovery](https://go-kratos.dev/docs/component/middleware/recovery) and more. - [Registry](https://go-kratos.dev/docs/component/registry) interface able to be connected with various other centralized registries through plug-ins. - The [standard log interfaces](https://go-kratos.dev/docs/component/log) ease the integration of the third-party log libs with logs collected through the *Fluentd*. - Automatically support the selection of the content [encoding](https://go-kratos.dev/docs/component/encoding) with Accept and Content-Type. - Multiple data sources are supported for [configurations](https://go-kratos.dev/docs/component/config) and dynamic configurations (use atomic operations). - In the protocol of HTTP/gRPC, use the uniform [metadata](https://go-kratos.dev/docs/component/metadata) transfer method. - You can define [errors](https://go-kratos.dev/docs/component/errors/) in protos and generate enums with protoc-gen-go. - You can define [verification rules](https://go-kratos.dev/docs/component/middleware/validate) in Protobuf supported by the HTTP/gRPC service. - [Swagger API](https://go-kratos.dev/docs/guide/openapi) is generated Automatically and embed Swagger UI endpoint can be started by adding [Swagger plugin](https://github.com/go-kratos/swagger-api). Kratos is accessible, powerful, and provides tools required for large, robust applications. ## Learning Kratos Kratos has the most extensive and thorough [documentation](https://go-kratos.dev/docs/getting-started/start) and [example](https://github.com/go-kratos/examples) library of all modern web application frameworks, making it a breeze to get started with the framework. We also provide a [modern template](https://github.com/go-kratos/kratos-layout). This template should help reduce the work required to set up modern projects. ### Goals Kratos boosts your productivity. With the integration of excellent resources and further support, programmers can get rid of most issues might encounter in the field of distributed systems and software engineering such that they are allowed to focus on the release of businesses only. Additionally, for each programmer, Kratos is also an ideal one learning warehouse for many aspects of microservices to enrich their experiences and skills. ### Principles * **Simple**: Appropriate design with plain and easy code. * **General**: Cover the various utilities for business development. * **Highly efficient**: Speeding up the efficiency of businesses upgrading. * **Stable**: The base libs validated in the production environment have the characteristics of high testability, high coverage as well as high security and reliability. * **Robust**: Eliminating misusing through high quality of the base libs. * **High-performance**: Optimal performance excluding the optimization of hacking in case of *unsafe*.  * **Expandability**: Properly designed interfaces where you can expand utilities such as base libs to meet your further requirements. * **Fault-tolerance**: Designed against failure, enhance the understanding and exercising of SRE within Kratos to achieve more robustness. * **Toolchain**: Includes an extensive toolchain, such as the code generation of cache, the lint tool, and so forth. ## Getting Started Create a kratos playground through [docker](https://www.docker.com/products/docker-desktop): ```shell docker run -it --rm -p 8000:8000 --workdir /workspace golang ``` ```shell apt-get update && apt-get -y install protobuf-compiler export GOPROXY=https://goproxy.io,direct go install github.com/go-kratos/kratos/cmd/kratos/v2@latest && kratos upgrade ``` ```shell kratos new helloworld cd helloworld/ && go mod tidy kratos run ``` Use a browser to open and visit: `http://localhost:8000/helloworld/kratos`, The kratos program is running! If you need more, please visit the kratos [documentation](https://go-kratos.dev/docs/getting-started/start). ## Security Vulnerabilities If you discover a security vulnerability within Kratos, please send an e-mail to tonybase via go-kratos@googlegroups.com. All security vulnerabilities will be promptly addressed. ## Community - [Wechat Group](https://github.com/go-kratos/kratos/issues/682) - [Discord Group](https://discord.gg/BWzJsUJ) - [go-kratos.dev](https://go-kratos.dev/en) ## Contributors Thank you for considering contributing to the Kratos framework! The contribution guide can be found in the [Kratos documentation](https://go-kratos.dev/docs/community/contribution). ## License The Kratos framework is open-sourced software licensed under the [MIT license](./LICENSE). ## Acknowledgments The following project had particular influence on kratos's design. - [go-kit/kit](https://github.com/go-kit/kit) is a programming toolkit for building microservices in go. - [asim/go-micro](https://github.com/asim/go-micro) a distributed systems development framework. - [google/go-cloud](https://github.com/google/go-cloud) is go cloud development kit. - [zeromicro/go-zero](https://github.com/zeromicro/go-zero) is a web and rpc framework with lots of builtin engineering practices. - [beego/beego](https://github.com/beego/beego) is a web framework including RESTful APIs, web apps and backend services. ================================================ FILE: README_zh.md ================================================

Build Status GoDoc codeCov Go Report Card License Awesome Go Discord

go-kratos%2Fkratos | Trendshift Go Kratos - A Go framework for microservices. | Product Hunt

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/zh-cn/docs/component/api) :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义; * [Errors](https://go-kratos.dev/zh-cn/docs/component/errors/) :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口; * [Metadata](https://go-kratos.dev/zh-cn/docs/component/metadata) :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递; * [Config](https://go-kratos.dev/zh-cn/docs/component/config) :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置; * [Logger](https://go-kratos.dev/zh-cn/docs/component/log) :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志; * [Metrics](https://go-kratos.dev/zh-cn/docs/component/middleware/metrics) :统一指标接口,可以实现各种指标系统,默认集成 Prometheus; * [Tracing](https://go-kratos.dev/zh-cn/docs/component/middleware/tracing) :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪; * [Encoding](https://go-kratos.dev/zh-cn/docs/component/encoding) :支持 Accept 和 Content-Type 进行自动选择内容编码; * [Transport](https://go-kratos.dev/zh-cn/docs/component/transport/overview) :通用的 [HTTP](https://go-kratos.dev/zh-cn/docs/component/transport/http) /[gRPC](https://go-kratos.dev/zh-cn/docs/component/transport/grpc) 传输层,实现统一的 [Middleware](https://go-kratos.dev/zh-cn/docs/component/middleware/overview) 插件支持; * [Registry](https://go-kratos.dev/zh-cn/docs/component/registry) :实现统一注册中心接口,可插件化对接各种注册中心; * [Validation](https://go-kratos.dev/zh-cn/docs/component/middleware/validate): 通过 Protobuf 统一定义校验规则,并同时适用于 HTTP/gRPC 服务; * [SwaggerAPI](https://go-kratos.dev/zh-cn/docs/guide/openapi): 通过集成第三方 [Swagger 插件](https://github.com/go-kratos/swagger-api) 能够自动生成 Swagger API 文档并启动内置的 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 [optional scope]: [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. ================================================ FILE: ROADMAP.md ================================================ # 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] Unary 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 ================================================ FILE: SECURITY.md ================================================ # 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. ================================================ FILE: api/README.md ================================================ # API proto ================================================ FILE: api/metadata/metadata.pb.go ================================================ // 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 } ================================================ FILE: api/metadata/metadata.proto ================================================ 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; } ================================================ FILE: api/metadata/metadata_grpc.pb.go ================================================ // 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", } ================================================ FILE: api/metadata/metadata_http.pb.go ================================================ // 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 } ================================================ FILE: api/metadata/server.go ================================================ 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 any) (*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 does not support 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 } ================================================ FILE: app.go ================================================ package kratos import ( "context" "errors" "os" "os/signal" "sync" "syscall" "time" "github.com/google/uuid" "golang.org/x/sync/errgroup" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/transport" ) // 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 context.CancelFunc 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, } 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 } } octx := NewContext(a.opts.ctx, a) for _, srv := range a.opts.servers { server := srv eg.Go(func() error { <-ctx.Done() // wait for stop signal stopCtx := context.WithoutCancel(octx) if a.opts.stopTimeout > 0 { var cancel context.CancelFunc stopCtx, cancel = context.WithTimeout(stopCtx, a.opts.stopTimeout) defer cancel() } return server.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 server.Start(octx) }) } 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 } err = nil 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 } ================================================ FILE: app_test.go ================================================ 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) } }) } } func TestApp_ContextCanceled(t *testing.T) { ctx, stop := context.WithCancel(context.Background()) stopFn := func(ctx context.Context) error { select { case <-ctx.Done(): t.Fatal("context should not be done yet") default: } return nil } app := New(Context(ctx), Server(&mockServer{stopFn: stopFn}), StopTimeout(time.Hour)) time.AfterFunc(time.Millisecond*10, stop) _ = app.Run() } ================================================ FILE: cmd/kratos/go.mod ================================================ module github.com/go-kratos/kratos/cmd/kratos/v2 go 1.23.0 toolchain go1.24.6 require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/charmbracelet/huh v0.8.0 github.com/emicklei/proto v1.10.0 github.com/fatih/color v1.13.0 github.com/spf13/cobra v1.4.0 golang.org/x/mod v0.17.0 golang.org/x/text v0.23.0 ) require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect github.com/charmbracelet/bubbletea v1.3.6 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/x/ansi v0.9.3 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.7.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect ) ================================================ FILE: cmd/kratos/go.sum ================================================ 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/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 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/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws= github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw= github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY= github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 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/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20210809222454-d867a43fc93e/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 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= ================================================ FILE: cmd/kratos/internal/base/install.go ================================================ 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 } ================================================ FILE: cmd/kratos/internal/base/mod.go ================================================ 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") } ================================================ FILE: cmd/kratos/internal/base/mod_test.go ================================================ 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.21` _, 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") }) } ================================================ FILE: cmd/kratos/internal/base/path.go ================================================ 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.ReplaceAll(path, dir+"/", ""), info.Size()) } return nil }) } ================================================ FILE: cmd/kratos/internal/base/repo.go ================================================ 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) } ================================================ FILE: cmd/kratos/internal/base/repo_test.go ================================================ 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") }) } ================================================ FILE: cmd/kratos/internal/base/vcs_url.go ================================================ 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") } ================================================ FILE: cmd/kratos/internal/base/vcs_url_test.go ================================================ 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) } ================================================ FILE: cmd/kratos/internal/change/change.go ================================================ 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)) } ================================================ FILE: cmd/kratos/internal/change/get.go ================================================ 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": {}, "build": {}, "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", "build", "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 "build": text = "### Builds\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["build"], 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) } ================================================ FILE: cmd/kratos/internal/change/get_test.go ================================================ 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) } } } ================================================ FILE: cmd/kratos/internal/project/add.go ================================================ 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, pkgPath 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) pkgPath = fmt.Sprintf("%s/%s", mod, pkgPath) repo := base.NewRepo(layout, branch) err := repo.CopyToV2(ctx, to, pkgPath, repoAddIgnores, []string{filepath.Join(p.Path, "api"), "api"}) if err != nil { return err } e := os.Rename( filepath.Join(to, "cmd", "server"), filepath.Join(to, "cmd", p.Name), ) if e != nil { if !os.IsNotExist(e) { 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 } ================================================ FILE: cmd/kratos/internal/project/new.go ================================================ 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 { if !os.IsNotExist(e) { 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 } ================================================ FILE: cmd/kratos/internal/project/project.go ================================================ package project import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "time" "github.com/AlecAivazis/survey/v2" "github.com/charmbracelet/huh" "github.com/spf13/cobra" "github.com/go-kratos/kratos/cmd/kratos/v2/internal/base" ) var projects = map[string]string{ "service": "https://github.com/go-kratos/kratos-layout.git", "admin": "https://github.com/go-kratos/kratos-admin.git", } // 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 ( nomod bool repo string branch string timeout = "60s" ) func init() { CmdNew.Flags().StringVarP(&repo, "repo", "r", repo, "custom repo url") 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) var repoURL string if repo != "" { repoURL = repo } else { repoURL, err = selectRepo() if err != nil { fmt.Fprintf(os.Stderr, "\033[31mERROR: failed to select repo(%s)\033[m\n", err.Error()) return } } 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 } packagePath, e := filepath.Rel(projectRoot, filepath.Join(workingDir, projectName)) if e != nil { done <- fmt.Errorf("🚫 failed to get relative path: %v", e) return } packagePath = strings.ReplaceAll(packagePath, "\\", "/") 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, packagePath) }() 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) } func selectRepo() (string, error) { var ( choice string customURL string ) form := huh.NewForm( // 1) Select group (always visible) huh.NewGroup( huh.NewSelect[string](). Title("Select a template"). Options( huh.NewOption("Service", "service"), huh.NewOption("Admin", "admin"), huh.NewOption("Custom (enter repo URL)", "custom"), ). Value(&choice), ), // 2) Input group (only visible when choice == "custom") huh.NewGroup( huh.NewInput(). Title("Enter custom repository URL"). Placeholder("https://github.com/owner/repo.git"). Value(&customURL). Validate(func(s string) error { s = strings.TrimSpace(s) if s == "" { return fmt.Errorf("repo URL cannot be empty") } return nil }), ).WithHideFunc(func() bool { return choice != "custom" }), ) if err := form.Run(); err != nil { panic(err) } if choice == "custom" { return strings.TrimSpace(customURL), nil } return projects[choice], nil } ================================================ FILE: cmd/kratos/internal/project/project_linux_test.go ================================================ //go: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) } }) } } ================================================ FILE: cmd/kratos/internal/project/project_test.go ================================================ 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 } ================================================ FILE: cmd/kratos/internal/project/project_windows_test.go ================================================ //go: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) } }) } } ================================================ FILE: cmd/kratos/internal/proto/add/add.go ================================================ 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, " ", "") } ================================================ FILE: cmd/kratos/internal/proto/add/add_test.go ================================================ 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) } }) } } ================================================ FILE: cmd/kratos/internal/proto/add/proto.go ================================================ 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) } ================================================ FILE: cmd/kratos/internal/proto/add/template.go ================================================ 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 } ================================================ FILE: cmd/kratos/internal/proto/client/client.go ================================================ 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, _ os.FileInfo, _ 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 } ================================================ FILE: cmd/kratos/internal/proto/proto.go ================================================ 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) } ================================================ FILE: cmd/kratos/internal/proto/server/server.go ================================================ 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: rpcName(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 exist\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 strings.TrimSuffix(toUpperCamelCase(strings.Split(name, ".")[0]), "Service") } func rpcName(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, " ", "") } ================================================ FILE: cmd/kratos/internal/proto/server/server_test.go ================================================ 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", }, { name: "serviceName with service suffix", args: args{str: "HelloWorldService"}, want: "HelloWorld", }, { name: "serviceName with space and service suffix", args: args{str: "Hello world service"}, want: "HelloWorld", }, { name: "serviceName with snake case and service suffix", args: args{str: "hello_world_service"}, 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) } }) } } ================================================ FILE: cmd/kratos/internal/proto/server/template.go ================================================ 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" }} {{- $s2 := "google_protobuf_Empty" }} {{ range .Methods }} {{- if eq .Type 1 }} func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if or (eq .Request $s1) (eq .Request $s2) }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if or (eq .Reply $s1) (eq .Reply $s2) }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) { return {{ if or (eq .Reply $s1) (eq .Reply $s2) }}&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 or (eq .Request $s1) (eq .Request $s2) }}*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" // another empty style const emptyV2 = "google_protobuf_Empty" buf := new(bytes.Buffer) for _, method := range s.Methods { isReqEmpty := method.Request == empty || method.Request == emptyV2 isReplyEmpty := method.Reply == empty || method.Reply == emptyV2 if (method.Type == unaryType && (isReqEmpty || isReplyEmpty)) || (method.Type == returnsStreamsType && isReqEmpty) { 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 } ================================================ FILE: cmd/kratos/internal/run/run.go ================================================ 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, _ 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 } base = 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 } } ================================================ FILE: cmd/kratos/internal/upgrade/upgrade.go ================================================ 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) } } ================================================ FILE: cmd/kratos/main.go ================================================ 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) } } ================================================ FILE: cmd/kratos/version.go ================================================ package main // release is the current kratos tool version. const release = "v2.9.2" ================================================ FILE: cmd/protoc-gen-go-errors/buf.gen.yaml ================================================ version: v2 plugins: - remote: buf.build/protocolbuffers/go:v1.33.0 out: . opt: - paths=source_relative ================================================ FILE: cmd/protoc-gen-go-errors/buf.yaml ================================================ version: v2 name: buf.build/go-kratos/protoc-gen-go-errors lint: use: - DEFAULT except: - PACKAGE_DIRECTORY_MATCH - ENUM_VALUE_UPPER_SNAKE_CASE - ENUM_VALUE_PREFIX - ENUM_ZERO_VALUE_SUFFIX - FIELD_LOWER_SNAKE_CASE deps: - buf.build/googleapis/googleapis breaking: use: - FILE ================================================ FILE: cmd/protoc-gen-go-errors/errors/errors.pb.go ================================================ // 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 } ================================================ FILE: cmd/protoc-gen-go-errors/errors/errors.proto ================================================ 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 metadata = 4; }; extend google.protobuf.EnumOptions { int32 default_code = 1108; } extend google.protobuf.EnumValueOptions { int32 code = 1109; } ================================================ FILE: cmd/protoc-gen-go-errors/errors.go ================================================ 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, "") } ================================================ FILE: cmd/protoc-gen-go-errors/errorsTemplate.tpl ================================================ {{ 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 }} ================================================ FILE: cmd/protoc-gen-go-errors/errors_test.go ================================================ 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) } }) } } ================================================ FILE: cmd/protoc-gen-go-errors/go.mod ================================================ module github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2 go 1.22 require ( golang.org/x/text v0.3.8 google.golang.org/protobuf v1.33.0 ) ================================================ FILE: cmd/protoc-gen-go-errors/go.sum ================================================ 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= 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/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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= ================================================ FILE: cmd/protoc-gen-go-errors/main.go ================================================ 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 }) } ================================================ FILE: cmd/protoc-gen-go-errors/template.go ================================================ 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() } ================================================ FILE: cmd/protoc-gen-go-errors/version.go ================================================ package main // release is the current protoc-gen-go-errors version. const release = "v2.9.2" ================================================ FILE: cmd/protoc-gen-go-http/go.mod ================================================ module github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2 go 1.22 require ( google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd google.golang.org/protobuf v1.33.0 ) ================================================ FILE: cmd/protoc-gen-go-http/go.sum ================================================ 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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= ================================================ FILE: cmd/protoc-gen-go-http/http.go ================================================ 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"), "//") } if m.Desc.Options().(*descriptorpb.MethodOptions).GetDeprecated() { if comment != "" { comment += "\n" } comment += deprecationComment } 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\b[\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." ================================================ FILE: cmd/protoc-gen-go-http/httpTemplate.tpl ================================================ {{$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 } {{- end}} if err := ctx.BindQuery(&in); err != nil { return err } {{- 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}} {{- if ne .Comment ""}} {{.Comment}} {{- end}} {{.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}} {{- if ne .Comment ""}} {{.Comment}} {{- end}} 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, nil } {{end}} ================================================ FILE: cmd/protoc-gen-go-http/http_test.go ================================================ 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"`) } } func TestReplaceBoundary(t *testing.T) { path := "/test/{message.namespace=*}/name/{message.name=*}" vars := buildPathVars(path) for v, s := range vars { if s != nil { path = replacePath(v, *s, path) } } if !reflect.DeepEqual("/test/{message.namespace:.*}/name/{message.name:.*}", path) { t.Fatal(`"/test/{message.namespace=*}/name/{message.name=*}" should be "/test/{message.namespace:.*}/name/{message.name:.*}"`) } } ================================================ FILE: cmd/protoc-gen-go-http/main.go ================================================ 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 }) } ================================================ FILE: cmd/protoc-gen-go-http/template.go ================================================ 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") } ================================================ FILE: cmd/protoc-gen-go-http/version.go ================================================ package main // release is the current protoc-gen-go-http version. const release = "v2.9.2" ================================================ FILE: codecov.yml ================================================ ignore: - "examples" - "**/*.pb.go" ================================================ FILE: config/README.md ================================================ # 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 ``` ================================================ FILE: config/config.go ================================================ package config import ( "context" "errors" "reflect" "sync" "time" "dario.cat/mergo" // 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 = errors.New("key not found") // ErrNotFound is key not found. // Observer is config observer. type Observer func(string, Value) // Config is a config interface. type Config interface { Load() error Scan(v any) 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, merge: func(dst, src any) error { return mergo.Map(dst, src, mergo.WithOverride) }, } 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 any) 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 any) 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 } // Get retrieves a config value by key and scans it into the target type. func Get[T any](c Config, key string) (T, error) { var t T v := c.Value(key) if v.Load() == nil { return t, ErrNotFound } switch any(t).(type) { case bool: b, err := v.Bool() return any(b).(T), err case int64: i, err := v.Int() return any(i).(T), err case int: i, err := v.Int() return any(int(i)).(T), err case float64: f, err := v.Float() return any(f).(T), err case string: s, err := v.String() return any(s).(T), err } err := v.Scan(&t) return t, err } ================================================ FILE: config/config_test.go ================================================ package config import ( "errors" "testing" "dario.cat/mergo" ) 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, merge: func(dst, src any) error { return mergo.Map(dst, src, mergo.WithOverride) }, } 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") } driverGet, err := Get[string](cf, "data.database.driver") if err != nil { t.Fatal(err) } if databaseDriver != driverGet { t.Errorf("Get[string] want: %s, got: %s", databaseDriver, driverGet) } type HTTPConfig struct { Addr string `json:"addr"` Port int `json:"port"` } v, err := Get[HTTPConfig](cf, "server.http") if err != nil { t.Fatal(err) } else if v.Addr != httpAddr { t.Errorf("Get[HttpConfig] Addr want: %s, got: %s", httpAddr, v.Addr) } else if v.Port != 80 { t.Errorf("Get[HttpConfig] Port want: 80, got: %d", v.Port) } err = cf.Watch("endpoints", func(string, 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") } } ================================================ FILE: config/env/env.go ================================================ 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() (kvs []*config.KeyValue, err error) { return e.load(os.Environ()), nil } func (e *env) load(envs []string) []*config.KeyValue { var kvs []*config.KeyValue for _, env := range envs { k, v, _ := strings.Cut(env, "=") if k == "" { continue } if len(e.prefixes) > 0 { prefix, ok := matchPrefix(e.prefixes, k) if !ok || k == prefix { continue } k = strings.TrimPrefix(k, prefix) k = strings.TrimPrefix(k, "_") } if k != "" { kvs = append(kvs, &config.KeyValue{ Key: k, Value: []byte(v), }) } } return kvs } 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 } ================================================ FILE: config/env/env_test.go ================================================ 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 any }{ { 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: []any{ map[string]any{ "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 any 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 any }{ { 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: []any{ map[string]any{ "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 any 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() } ================================================ FILE: config/env/watcher.go ================================================ 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 } ================================================ FILE: config/env/watcher_test.go ================================================ 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() }) } ================================================ FILE: config/file/file.go ================================================ 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) } ================================================ FILE: config/file/file_test.go ================================================ 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]any{ "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() } ================================================ FILE: config/file/format.go ================================================ package file import "strings" func format(name string) string { if idx := strings.LastIndexByte(name, '.'); idx >= 0 { return name[idx+1:] } return "" } ================================================ FILE: config/file/format_test.go ================================================ 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: "a.", expect: "", }, { input: ".b", expect: "b", }, { input: "a.b", expect: "b", }, { input: "a.b.c", expect: "c", }, } for _, v := range tests { content := format(v.input) if got, want := content, v.expect; got != want { t.Errorf("expect %v,got %v", want, got) } } } func BenchmarkFormat(b *testing.B) { for i := 0; i < b.N; i++ { format("abc.txt") } } ================================================ FILE: config/file/watcher.go ================================================ 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() } ================================================ FILE: config/options.go ================================================ package config import ( "fmt" "regexp" "strconv" "strings" "github.com/go-kratos/kratos/v2/encoding" ) // Decoder is config decoder. type Decoder func(*KeyValue, map[string]any) error // Resolver resolve placeholder in config. type Resolver func(map[string]any) error // Merge is config merge func. type Merge func(dst, src any) error // Option is config option. type Option func(*options) type options struct { sources []Source decoder Decoder resolver Resolver merge Merge } // 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 } } // WithResolveActualTypes with config resolver. // bool input will enable conversion of config to data types func WithResolveActualTypes(enableConvertToType bool) Option { return func(o *options) { o.resolver = newActualTypesResolver(enableConvertToType) } } // WithResolver with config resolver. func WithResolver(r Resolver) Option { return func(o *options) { o.resolver = r } } // WithMergeFunc with config merge func. func WithMergeFunc(m Merge) Option { return func(o *options) { o.merge = m } } // defaultDecoder decode config from source KeyValue // to target map[string]interface{} using src.Format codec. func defaultDecoder(src *KeyValue, target map[string]any) 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]any) 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) } func newActualTypesResolver(enableConvertToType bool) func(map[string]any) error { return func(input map[string]any) error { mapper := mapper(input) return resolver(input, mapper, enableConvertToType) } } // defaultResolver resolve placeholder in map value, // placeholder format in ${key:default}. func defaultResolver(input map[string]any) error { mapper := mapper(input) return resolver(input, mapper, false) } func resolver(input map[string]any, mapper func(name string) string, toType bool) error { var resolve func(map[string]any) error resolve = func(sub map[string]any) error { for k, v := range sub { switch vt := v.(type) { case string: sub[k] = expand(vt, mapper, toType) case map[string]any: if err := resolve(vt); err != nil { return err } case []any: for i, iface := range vt { switch it := iface.(type) { case string: vt[i] = expand(it, mapper, toType) case map[string]any: if err := resolve(it); err != nil { return err } } } sub[k] = vt } } return nil } return resolve(input) } func mapper(input map[string]any) func(name string) string { mapper := func(name string) string { args := strings.SplitN(strings.TrimSpace(name), ":", 2) //nolint:mnd if v, has := readValue(input, args[0]); has { s, _ := v.String() return s } else if len(args) > 1 { // default value return args[1] } return "" } return mapper } func convertToType(input string) any { // Check if the input is a string with quotes if strings.HasPrefix(input, "\"") && strings.HasSuffix(input, "\"") { // Trim the quotes and return the string value return strings.Trim(input, "\"") } // Try converting to bool if input == "true" || input == "false" { b, _ := strconv.ParseBool(input) return b } // Try converting to float64 if strings.Contains(input, ".") { if f, err := strconv.ParseFloat(input, 64); err == nil { return f } } // Try converting to int64 if i, err := strconv.ParseInt(input, 10, 64); err == nil { return i } // Default to string if no other conversion succeeds return input } // placeholderRegexp matches ${...} placeholders in config value var placeholderRegexp = regexp.MustCompile(`\${(.*?)}`) func expand(s string, mapping func(string) string, toType bool) any { re := placeholderRegexp.FindAllStringSubmatch(s, -1) var ct any for _, i := range re { if len(i) == 2 { //nolint:mnd m := mapping(i[1]) if toType { ct = convertToType(m) return ct } s = strings.ReplaceAll(s, i[0], m) } } return s } ================================================ FILE: config/options_test.go ================================================ package config import ( "reflect" "strings" "testing" ) func TestDefaultDecoder(t *testing.T) { src := &KeyValue{ Key: "service", Value: []byte("config"), Format: "", } target := make(map[string]any) err := defaultDecoder(src, target) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(target, map[string]any{"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]any) err = defaultDecoder(src, target) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(map[string]any{ "service": map[string]any{ "name": map[string]any{ "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]any{ "foo": map[string]any{ "bar": map[string]any{ "notexist": "${NOTEXIST:100}", "port": "${PORT:8081}", "count": "${COUNT:0}", "enable": "${ENABLE:false}", "rate": "${RATE}", "empty": "${EMPTY:foobar}", "url": "${URL:http://example.com}", "array": []any{ "${PORT}", map[string]any{"foobar": "${NOTEXIST:8081}"}, }, "value1": "${test.value}", "value2": "$PORT", "value3": "abc${PORT}foo${COUNT}bar", "value4": "${foo${bar}}", }, }, "test": map[string]any{ "value": "foobar", }, "PORT": "8080", "COUNT": "10", "ENABLE": "true", "RATE": "0.9", "EMPTY": "", } tests := []struct { name string path string expect any }{ { 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: []any{portString, map[string]any{"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 any 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, actual: %#v", test.expect, actual) t.Fail() } } if err != nil { t.Error(err) } } else { t.Error("value path not found") } }) } } func TestNewDefaultResolver(t *testing.T) { var ( portString = "8080" countInt = 10 rateFloat = 0.9 ) data := map[string]any{ "foo": map[string]any{ "bar": map[string]any{ "notexist": "${NOTEXIST:100}", "port": "${PORT:\"8081\"}", "count": "${COUNT:\"0\"}", "enable": "${ENABLE:false}", "rate": "${RATE}", "empty": "${EMPTY:foobar}", "url": "${URL:\"http://example.com\"}", "array": []any{ "${PORT}", map[string]any{"foobar": "${NOTEXIST:\"8081\"}"}, }, "value1": "${test.value}", "value2": "$PORT", "value3": "abc${PORT}foo${COUNT}bar", "value4": "${foo${bar}}", }, }, "test": map[string]any{ "value": "foobar", }, "PORT": "\"8080\"", "COUNT": "\"10\"", "ENABLE": "true", "RATE": "0.9", "EMPTY": "", } tests := []struct { name string path string expect any }{ { 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: []any{portString, map[string]any{"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) { fn := newActualTypesResolver(true) err := fn(data) if err != nil { t.Fatal(err) } rd := reader{ values: data, } if v, ok := rd.Value(test.path); ok { var actual any 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, actual: %#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, false); got != tt.want { t.Errorf("expand() want: %s, got: %s", tt.want, got) } } } func TestWithMergeFunc(t *testing.T) { c := &options{} a := func(any, any) error { return nil } WithMergeFunc(a)(c) if c.merge == nil { t.Fatal("c.merge is nil") } } ================================================ FILE: config/reader.go ================================================ package config import ( "bytes" "encoding/gob" "encoding/json" "fmt" "strings" "sync" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/log" ) // Reader is config reader. type Reader interface { Merge(...*KeyValue) error Value(string) (Value, bool) Source() ([]byte, error) Resolve() error } type reader struct { opts options values map[string]any lock sync.Mutex } func newReader(opts options) Reader { return &reader{ opts: opts, values: make(map[string]any), lock: sync.Mutex{}, } } func (r *reader) Merge(kvs ...*KeyValue) error { merged, err := r.cloneMap() if err != nil { return err } for _, kv := range kvs { next := make(map[string]any) if err := r.opts.decoder(kv, next); err != nil { log.Errorf("Failed to config decode error: %v key: %s value: %s", err, kv.Key, string(kv.Value)) return err } if err := r.opts.merge(&merged, convertMap(next)); err != nil { log.Errorf("Failed to config merge error: %v key: %s value: %s", err, kv.Key, string(kv.Value)) return err } } r.lock.Lock() r.values = merged r.lock.Unlock() return nil } func (r *reader) Value(path string) (Value, bool) { r.lock.Lock() defer r.lock.Unlock() return readValue(r.values, path) } func (r *reader) Source() ([]byte, error) { r.lock.Lock() defer r.lock.Unlock() return marshalJSON(convertMap(r.values)) } func (r *reader) Resolve() error { r.lock.Lock() defer r.lock.Unlock() return r.opts.resolver(r.values) } func (r *reader) cloneMap() (map[string]any, error) { r.lock.Lock() defer r.lock.Unlock() return cloneMap(r.values) } func cloneMap(src map[string]any) (map[string]any, error) { // https://gist.github.com/soroushjp/0ec92102641ddfc3ad5515ca76405f4d var buf bytes.Buffer gob.Register(map[string]any{}) gob.Register([]any{}) enc := gob.NewEncoder(&buf) dec := gob.NewDecoder(&buf) err := enc.Encode(src) if err != nil { return nil, err } var clone map[string]any err = dec.Decode(&clone) if err != nil { return nil, err } return clone, nil } func convertMap(src any) any { switch m := src.(type) { case map[string]any: dst := make(map[string]any, len(m)) for k, v := range m { dst[k] = convertMap(v) } return dst case map[any]any: dst := make(map[string]any, len(m)) for k, v := range m { dst[fmt.Sprint(k)] = convertMap(v) } return dst case []any: dst := make([]any, len(m)) for k, v := range m { dst[k] = convertMap(v) } return dst case []byte: // there will be no binary data in the config data return string(m) default: return src } } // readValue read Value in given map[string]interface{} // by the given path, will return false if not found. func readValue(values map[string]any, path string) (Value, bool) { var ( next = values keys = strings.Split(path, ".") last = len(keys) - 1 ) for idx, key := range keys { value, ok := next[key] if !ok { return nil, false } if idx == last { av := &atomicValue{} av.Store(value) return av, true } switch vm := value.(type) { case map[string]any: next = vm default: return nil, false } } return nil, false } func marshalJSON(v any) ([]byte, error) { if m, ok := v.(proto.Message); ok { return protojson.MarshalOptions{EmitUnpopulated: true}.Marshal(m) } return json.Marshal(v) } func unmarshalJSON(data []byte, v any) error { if m, ok := v.(proto.Message); ok { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(data, m) } return json.Unmarshal(data, v) } ================================================ FILE: config/reader_test.go ================================================ package config import ( "fmt" "reflect" "testing" "github.com/go-kratos/kratos/v2/encoding" "dario.cat/mergo" ) func TestReader_Merge(t *testing.T) { var ( err error ok bool ) opts := options{ decoder: func(kv *KeyValue, v map[string]any) error { if codec := encoding.GetCodec(kv.Format); codec != nil { return codec.Unmarshal(kv.Value, &v) } return fmt.Errorf("unsupported key: %s format: %s", kv.Key, kv.Format) }, resolver: defaultResolver, merge: func(dst, src any) error { return mergo.Map(dst, src, mergo.WithOverride) }, } r := newReader(opts) err = r.Merge(&KeyValue{ Key: "a", Value: []byte("bad"), Format: "json", }) if err == nil { t.Fatal("err is nil") } err = r.Merge(&KeyValue{ Key: "b", Value: []byte(`{"nice": "boat", "x": 1}`), Format: "json", }) if err != nil { t.Fatal(err) } vv, ok := r.Value("nice") if !ok { t.Fatal("ok is false") } vvv, err := vv.String() if err != nil { t.Fatal(err) } if vvv != "boat" { t.Fatal(`vvv is not equal to "boat"`) } err = r.Merge(&KeyValue{ Key: "b", Value: []byte(`{"x": 2}`), Format: "json", }) if err != nil { t.Fatal(err) } vv, ok = r.Value("x") if !ok { t.Fatal("ok is false") } vvx, err := vv.Int() if err != nil { t.Fatal(err) } if vvx != 2 { t.Fatal("vvx is not equal to 2") } } func TestReader_Value(t *testing.T) { opts := options{ decoder: func(kv *KeyValue, v map[string]any) error { if codec := encoding.GetCodec(kv.Format); codec != nil { return codec.Unmarshal(kv.Value, &v) } return fmt.Errorf("unsupported key: %s format: %s", kv.Key, kv.Format) }, resolver: defaultResolver, merge: func(dst, src any) error { return mergo.Map(dst, src, mergo.WithOverride) }, } ymlval := ` a: b: X: 1 Y: "lol" z: true ` tests := []struct { name string kv KeyValue }{ { name: "json value", kv: KeyValue{ Key: "config", Value: []byte(`{"a": {"b": {"X": 1, "Y": "lol", "z": true}}}`), Format: "json", }, }, { name: "yaml value", kv: KeyValue{ Key: "config", Value: []byte(ymlval), Format: "yaml", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { r := newReader(opts) err := r.Merge(&test.kv) if err != nil { t.Fatal(err) } vv, ok := r.Value("a.b.X") if !ok { t.Fatal("ok is false") } vvv, err := vv.Int() if err != nil { t.Fatal(err) } if int64(1) != vvv { t.Fatal("vvv is not equal to 1") } vv, ok = r.Value("a.b.Y") if !ok { t.Fatal("ok is false") } vvy, err := vv.String() if err != nil { t.Fatal(err) } if vvy != "lol" { t.Fatal(`vvy is not equal to "lol"`) } vv, ok = r.Value("a.b.z") if !ok { t.Fatal("ok is false") } vvz, err := vv.Bool() if err != nil { t.Fatal(err) } if !vvz { t.Fatal("vvz is not equal to true") } _, ok = r.Value("aasasdg=234l.asdfk,") if ok { t.Fatal("ok is true") } _, ok = r.Value("aas......asdg=234l.asdfk,") if ok { t.Fatal("ok is true") } _, ok = r.Value("a.b.Y.") if ok { t.Fatal("ok is true") } }) } } func TestReader_Source(t *testing.T) { var err error opts := options{ decoder: func(kv *KeyValue, v map[string]any) error { if codec := encoding.GetCodec(kv.Format); codec != nil { return codec.Unmarshal(kv.Value, &v) } return fmt.Errorf("unsupported key: %s format: %s", kv.Key, kv.Format) }, resolver: defaultResolver, merge: func(dst, src any) error { return mergo.Map(dst, src, mergo.WithOverride) }, } r := newReader(opts) err = r.Merge(&KeyValue{ Key: "b", Value: []byte(`{"a": {"b": {"X": 1}}}`), Format: "json", }) if err != nil { t.Fatal(err) } b, err := r.Source() if err != nil { t.Fatal(err) } if !reflect.DeepEqual([]byte(`{"a":{"b":{"X":1}}}`), b) { t.Fatal("[]byte(`{\"a\":{\"b\":{\"X\":1}}}`) is not equal to b") } } func TestCloneMap(t *testing.T) { tests := []struct { input map[string]any want map[string]any }{ { input: map[string]any{ "a": 1, "b": "2", "c": true, }, want: map[string]any{ "a": 1, "b": "2", "c": true, }, }, { input: map[string]any{}, want: map[string]any{}, }, { input: nil, want: map[string]any{}, }, } for _, tt := range tests { if got, err := cloneMap(tt.input); err != nil { t.Errorf("expect no err, got %v", err) } else if !reflect.DeepEqual(got, tt.want) { t.Errorf("cloneMap(%v) = %v, want %v", tt.input, got, tt.want) } } } func TestConvertMap(t *testing.T) { tests := []struct { input any want any }{ { input: map[string]any{ "a": 1, "b": "2", "c": true, "d": []byte{65, 66, 67}, }, want: map[string]any{ "a": 1, "b": "2", "c": true, "d": "ABC", }, }, { input: []any{1, 2.0, "3", true, nil, []any{1, 2.0, "3", true, nil}}, want: []any{1, 2.0, "3", true, nil, []any{1, 2.0, "3", true, nil}}, }, { input: []byte{65, 66, 67}, want: "ABC", }, } for _, tt := range tests { if got := convertMap(tt.input); !reflect.DeepEqual(got, tt.want) { t.Errorf("convertMap(%v) = %v, want %v", tt.input, got, tt.want) } } } func TestReadValue(t *testing.T) { m := map[string]any{ "a": 1, "b": map[string]any{ "c": "3", "d": map[string]any{ "e": true, }, }, } va := atomicValue{} va.Store(1) vbc := atomicValue{} vbc.Store("3") vbde := atomicValue{} vbde.Store(true) tests := []struct { path string want atomicValue }{ { path: "a", want: va, }, { path: "b.c", want: vbc, }, { path: "b.d.e", want: vbde, }, } for _, tt := range tests { if got, found := readValue(m, tt.path); !found { t.Errorf("expect found %v in %v, but not.", tt.path, m) } else if got.Load() != tt.want.Load() { t.Errorf("readValue(%v, %v) = %v, want %v", m, tt.path, got, tt.want) } } } ================================================ FILE: config/source.go ================================================ package config // KeyValue is config key value. type KeyValue struct { Key string Value []byte Format string } // Source is config source. type Source interface { Load() ([]*KeyValue, error) Watch() (Watcher, error) } // Watcher watches a source for changes. type Watcher interface { Next() ([]*KeyValue, error) Stop() error } ================================================ FILE: config/value.go ================================================ package config import ( "encoding/json" "fmt" "reflect" "strconv" "sync/atomic" "time" "google.golang.org/protobuf/proto" kratosjson "github.com/go-kratos/kratos/v2/encoding/json" ) var ( _ Value = (*atomicValue)(nil) _ Value = (*errValue)(nil) ) // Value is config value interface. type Value interface { Bool() (bool, error) Int() (int64, error) Float() (float64, error) String() (string, error) Duration() (time.Duration, error) Slice() ([]Value, error) Map() (map[string]Value, error) Scan(any) error Load() any Store(any) } type atomicValue struct { atomic.Value } func (v *atomicValue) typeAssertError() error { return fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load())) } func (v *atomicValue) Bool() (bool, error) { switch val := v.Load().(type) { case bool: return val, nil case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: return strconv.ParseBool(fmt.Sprint(val)) case string: return strconv.ParseBool(val) } return false, v.typeAssertError() } func (v *atomicValue) Int() (int64, error) { switch val := v.Load().(type) { case int: return int64(val), nil case int8: return int64(val), nil case int16: return int64(val), nil case int32: return int64(val), nil case int64: return val, nil case uint: return int64(val), nil case uint8: return int64(val), nil case uint16: return int64(val), nil case uint32: return int64(val), nil case uint64: return int64(val), nil case float32: return int64(val), nil case float64: return int64(val), nil case string: return strconv.ParseInt(val, 10, 64) } return 0, v.typeAssertError() } func (v *atomicValue) Slice() ([]Value, error) { vals, ok := v.Load().([]any) if !ok { return nil, v.typeAssertError() } slices := make([]Value, 0, len(vals)) for _, val := range vals { a := new(atomicValue) a.Store(val) slices = append(slices, a) } return slices, nil } func (v *atomicValue) Map() (map[string]Value, error) { vals, ok := v.Load().(map[string]any) if !ok { return nil, v.typeAssertError() } m := make(map[string]Value, len(vals)) for key, val := range vals { a := new(atomicValue) a.Store(val) m[key] = a } return m, nil } func (v *atomicValue) Float() (float64, error) { switch val := v.Load().(type) { case int: return float64(val), nil case int8: return float64(val), nil case int16: return float64(val), nil case int32: return float64(val), nil case int64: return float64(val), nil case uint: return float64(val), nil case uint8: return float64(val), nil case uint16: return float64(val), nil case uint32: return float64(val), nil case uint64: return float64(val), nil case float32: return float64(val), nil case float64: return val, nil case string: return strconv.ParseFloat(val, 64) } return 0.0, v.typeAssertError() } func (v *atomicValue) String() (string, error) { switch val := v.Load().(type) { case string: return val, nil case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: return fmt.Sprint(val), nil case []byte: return string(val), nil case fmt.Stringer: return val.String(), nil } return "", v.typeAssertError() } func (v *atomicValue) Duration() (time.Duration, error) { val, err := v.Int() if err != nil { return 0, err } return time.Duration(val), nil } func (v *atomicValue) Scan(obj any) error { data, err := json.Marshal(v.Load()) if err != nil { return err } if pb, ok := obj.(proto.Message); ok { return kratosjson.UnmarshalOptions.Unmarshal(data, pb) } return json.Unmarshal(data, obj) } type errValue struct { err error } func (v errValue) Bool() (bool, error) { return false, v.err } func (v errValue) Int() (int64, error) { return 0, v.err } func (v errValue) Float() (float64, error) { return 0.0, v.err } func (v errValue) Duration() (time.Duration, error) { return 0, v.err } func (v errValue) String() (string, error) { return "", v.err } func (v errValue) Scan(any) error { return v.err } func (v errValue) Load() any { return nil } func (v errValue) Store(any) {} func (v errValue) Slice() ([]Value, error) { return nil, v.err } func (v errValue) Map() (map[string]Value, error) { return nil, v.err } ================================================ FILE: config/value_test.go ================================================ package config import ( "fmt" "testing" "time" ) func TestAtomicValue_Bool(t *testing.T) { vlist := []any{"1", "t", "T", "true", "TRUE", "True", true, 1, int32(1)} for _, x := range vlist { v := atomicValue{} v.Store(x) b, err := v.Bool() if err != nil { t.Fatal(err) } if !b { t.Fatal("b is not equal to true") } } vlist = []any{"0", "f", "F", "false", "FALSE", "False", false, 0, int32(0)} for _, x := range vlist { v := atomicValue{} v.Store(x) b, err := v.Bool() if err != nil { t.Fatal(err) } if b { t.Fatal("b is not equal to false") } } vlist = []any{"bbb", "-1"} for _, x := range vlist { v := atomicValue{} v.Store(x) _, err := v.Bool() if err == nil { t.Fatal("err is nil") } } } func TestAtomicValue_Int(t *testing.T) { vlist := []any{"123123", float64(123123), int64(123123), int32(123123), 123123} for _, x := range vlist { v := atomicValue{} v.Store(x) b, err := v.Int() if err != nil { t.Fatal(err) } if b != 123123 { t.Fatal("b is not equal to 123123") } } vlist = []any{"bbb", "-x1", true} for _, x := range vlist { v := atomicValue{} v.Store(x) _, err := v.Int() if err == nil { t.Fatal("err is nil") } } } func TestAtomicValue_Float(t *testing.T) { vlist := []any{"123123.1", 123123.1} for _, x := range vlist { v := atomicValue{} v.Store(x) b, err := v.Float() if err != nil { t.Fatal(err) } if b != 123123.1 { t.Fatal("b is not equal to 123123.1") } } vlist = []any{"bbb", "-x1"} for _, x := range vlist { v := atomicValue{} v.Store(x) _, err := v.Float() if err == nil { t.Fatal("err is nil") } } } type ts struct { Name string Age int } func (t ts) String() string { return fmt.Sprintf("%s%d", t.Name, t.Age) } func TestAtomicValue_String(t *testing.T) { vlist := []any{"1", float64(1), int64(1), 1, int64(1)} for _, x := range vlist { v := atomicValue{} v.Store(x) b, err := v.String() if err != nil { t.Fatal(err) } if b != "1" { t.Fatal("b is not equal to 1") } } v := atomicValue{} v.Store(true) b, err := v.String() if err != nil { t.Fatal(err) } if b != "true" { t.Fatal(`b is not equal to "true"`) } v = atomicValue{} v.Store(ts{ Name: "test", Age: 10, }) b, err = v.String() if err != nil { t.Fatal(err) } if b != "test10" { t.Fatal(`b is not equal to "test10"`) } } func TestAtomicValue_Duration(t *testing.T) { vlist := []any{int64(5)} for _, x := range vlist { v := atomicValue{} v.Store(x) b, err := v.Duration() if err != nil { t.Fatal(err) } if b != time.Duration(5) { t.Fatal("b is not equal to time.Duration(5)") } } } func TestAtomicValue_Slice(t *testing.T) { vlist := []any{int64(5)} v := atomicValue{} v.Store(vlist) slices, err := v.Slice() if err != nil { t.Fatal(err) } for _, v := range slices { b, err := v.Duration() if err != nil { t.Fatal(err) } if b != time.Duration(5) { t.Fatal("b is not equal to time.Duration(5)") } } } func TestAtomicValue_Map(t *testing.T) { vlist := make(map[string]any) vlist["5"] = int64(5) vlist["text"] = "text" v := atomicValue{} v.Store(vlist) m, err := v.Map() if err != nil { t.Fatal(err) } for k, v := range m { if k == "5" { b, err := v.Duration() if err != nil { t.Fatal(err) } if b != time.Duration(5) { t.Fatal("b is not equal to time.Duration(5)") } } else { b, err := v.String() if err != nil { t.Fatal(err) } if b != "text" { t.Fatal(`b is not equal to "text"`) } } } } func TestAtomicValue_Scan(t *testing.T) { v := atomicValue{} err := v.Scan(&struct { A string `json:"a"` }{"a"}) if err != nil { t.Fatal(err) } err = v.Scan(&struct { A string `json:"a"` }{"a"}) if err != nil { t.Fatal(err) } } ================================================ FILE: contrib/config/apollo/README.md ================================================ ## Apollo config center This module implements the `config.Source` interface in kratos based apollo config management center. [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/go-kratos/kratos/contrib/config/apollo/v2) ### Quick start ```go import ( "fmt" "log" "github.com/go-kratos/kratos/contrib/config/apollo/v2" "github.com/go-kratos/kratos/v2/config" ) func main() { c := config.New( config.WithSource( apollo.NewSource( apollo.WithAppID("kratos"), apollo.WithCluster("dev"), apollo.WithEndpoint("http://localhost:8080"), apollo.WithNamespace("application,event.yaml,demo.json"), apollo.WithEnableBackup(), apollo.WithSecret("ad75b33c77ae4b9c9626d969c44f41ee"), ), ), ) var bc bootstrap if err := c.Load(); err != nil { panic(err) } // use value and watch operations,help yourself. } ``` ### Options list > You get what you see. ```go // specify the app id func WithAppID(appID string) Option // specify the cluster of application func WithCluster(cluster string) Option // enable backup or not, and where to back up them. func WithBackupPath(backupPath string) Option func WithDisableBackup() Option func WithEnableBackup() Option // specify apollo endpoint, such as http://localhost:8080 func WithEndpoint(endpoint string) Option // namespaces to load, comma to separate. func WithNamespace(name string) Option // secret is the apollo secret key to access application config. func WithSecret(secret string) Option ``` ### Notice apollo config center use `Namespace` to be part of the key. For example: ***application.json*** ```json { "http": { "address": ":8080", "tls": { "enable": false, "cert_file": "", "key_file": "" } } } ``` you got them in kratos config instance maybe look like: ```go config := map[string]interface{}{ // application be part of the key path. "application": map[string]interface{}{ "http": map[string]interface{}{ "address": ":8080", "tls": map[string]interface{}{ "enable": false, "cert_file": "", "key_file": "", }, }, }, } ``` ================================================ FILE: contrib/config/apollo/apollo.go ================================================ package apollo import ( "strings" "github.com/apolloconfig/agollo/v4" "github.com/apolloconfig/agollo/v4/constant" apolloconfig "github.com/apolloconfig/agollo/v4/env/config" "github.com/apolloconfig/agollo/v4/extension" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/log" ) type apollo struct { client agollo.Client opt *options } const ( yaml = "yaml" yml = "yml" json = "json" properties = "properties" ) var formats map[string]struct{} // Option is apollo option type Option func(*options) type options struct { appid string secret string cluster string endpoint string namespace string isBackupConfig bool backupPath string originConfig bool } // WithAppID with apollo config app id func WithAppID(appID string) Option { return func(o *options) { o.appid = appID } } // WithCluster with apollo config cluster func WithCluster(cluster string) Option { return func(o *options) { o.cluster = cluster } } // WithEndpoint with apollo config conf server ip func WithEndpoint(endpoint string) Option { return func(o *options) { o.endpoint = endpoint } } // WithEnableBackup with apollo config enable backup config func WithEnableBackup() Option { return func(o *options) { o.isBackupConfig = true } } // WithDisableBackup with apollo config enable backup config func WithDisableBackup() Option { return func(o *options) { o.isBackupConfig = false } } // WithSecret with apollo config app secret func WithSecret(secret string) Option { return func(o *options) { o.secret = secret } } // WithNamespace with apollo config namespace name func WithNamespace(name string) Option { return func(o *options) { o.namespace = name } } // WithBackupPath with apollo config backupPath func WithBackupPath(backupPath string) Option { return func(o *options) { o.backupPath = backupPath } } // WithOriginalConfig use the original configuration file without parse processing func WithOriginalConfig() Option { return func(o *options) { extension.AddFormatParser(constant.JSON, &jsonExtParser{}) extension.AddFormatParser(constant.YAML, &yamlExtParser{}) extension.AddFormatParser(constant.YML, &yamlExtParser{}) o.originConfig = true } } func NewSource(opts ...Option) config.Source { op := options{} for _, o := range opts { o(&op) } client, err := agollo.StartWithConfig(func() (*apolloconfig.AppConfig, error) { return &apolloconfig.AppConfig{ AppID: op.appid, Cluster: op.cluster, NamespaceName: op.namespace, IP: op.endpoint, IsBackupConfig: op.isBackupConfig, Secret: op.secret, BackupConfigPath: op.backupPath, }, nil }) if err != nil { panic(err) } return &apollo{client: client, opt: &op} } func format(ns string) string { arr := strings.Split(ns, ".") suffix := arr[len(arr)-1] if len(arr) <= 1 || suffix == properties { return json } if _, ok := formats[suffix]; !ok { // fallback return json } return suffix } func (e *apollo) load() []*config.KeyValue { kvs := make([]*config.KeyValue, 0) namespaces := strings.Split(e.opt.namespace, ",") for _, ns := range namespaces { if !e.opt.originConfig { kv, err := e.getConfig(ns) if err != nil { log.Errorf("apollo get config failed,err:%v", err) continue } kvs = append(kvs, kv) continue } if strings.Contains(ns, ".") && !strings.HasSuffix(ns, "."+properties) && (format(ns) == yaml || format(ns) == yml || format(ns) == json) { kv, err := e.getOriginConfig(ns) if err != nil { log.Errorf("apollo get config failed,err:%v", err) continue } kvs = append(kvs, kv) continue } kv, err := e.getConfig(ns) if err != nil { log.Errorf("apollo get config failed,err:%v", err) continue } kvs = append(kvs, kv) } return kvs } func (e *apollo) getConfig(ns string) (*config.KeyValue, error) { next := map[string]any{} e.client.GetConfigCache(ns).Range(func(key, value any) bool { // all values are out properties format resolve(genKey(ns, key.(string)), value, next) return true }) f := format(ns) codec := encoding.GetCodec(f) val, err := codec.Marshal(next) if err != nil { return nil, err } return &config.KeyValue{ Key: ns, Value: val, Format: f, }, nil } func (e apollo) getOriginConfig(ns string) (*config.KeyValue, error) { value, err := e.client.GetConfigCache(ns).Get("content") if err != nil { return nil, err } // serialize the namespace content KeyValue into bytes. return &config.KeyValue{ Key: ns, Value: []byte(value.(string)), Format: format(ns), }, nil } func (e *apollo) Load() (kv []*config.KeyValue, err error) { return e.load(), nil } func (e *apollo) Watch() (config.Watcher, error) { w, err := newWatcher(e) if err != nil { return nil, err } return w, nil } // resolve convert kv pair into one map[string]interface{} by split key into different // map level. such as: app.name = "application" => map[app][name] = "application" func resolve(key string, value any, target map[string]any) { // expand key "aaa.bbb" into map[aaa]map[bbb]interface{} keys := strings.Split(key, ".") last := len(keys) - 1 cursor := target for i, k := range keys { if i == last { cursor[k] = value break } // not the last key, be deeper v, ok := cursor[k] if !ok { // create a new map deeper := make(map[string]any) cursor[k] = deeper cursor = deeper continue } // current exists, then check existing value type, if it's not map // that means duplicate keys, and at least one is not map instance. if cursor, ok = v.(map[string]any); !ok { log.Warnf("duplicate key: %v\n", strings.Join(keys[:i+1], ".")) break } } } // genKey got the key of config.KeyValue pair. // eg: namespace.ext with subKey got namespace.subKey func genKey(ns, sub string) string { arr := strings.Split(ns, ".") if len(arr) == 1 { if ns == "" { return sub } return ns + "." + sub } suffix := arr[len(arr)-1] _, ok := formats[suffix] if ok { return strings.Join(arr[:len(arr)-1], ".") + "." + sub } return ns + "." + sub } func init() { formats = make(map[string]struct{}) formats[yaml] = struct{}{} formats[yml] = struct{}{} formats[json] = struct{}{} formats[properties] = struct{}{} } ================================================ FILE: contrib/config/apollo/apollo_test.go ================================================ package apollo import ( "testing" ) func Test_genKey(t *testing.T) { type args struct { ns string sub string } tests := []struct { name string args args want string }{ { name: "blank namespace", args: args{ ns: "", sub: "x.y", }, want: "x.y", }, { name: "properties namespace", args: args{ ns: "application", sub: "x.y", }, want: "application.x.y", }, { name: "namespace with format", args: args{ ns: "app.yaml", sub: "x.y", }, want: "app.x.y", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := genKey(tt.args.ns, tt.args.sub); got != tt.want { t.Errorf("genKey() = %v, want %v", got, tt.want) } }) } } func Test_format(t *testing.T) { tests := []struct { name string namespace string want string }{ { name: "properties namespace", namespace: "application", want: "json", }, { name: "properties namespace #1", namespace: "app.setting", want: "json", }, { name: "namespace with format[yaml]", namespace: "app.yaml", want: "yaml", }, { name: "namespace with format[yml]", namespace: "app.yml", want: "yml", }, { name: "namespace with format[json]", namespace: "app.json", want: "json", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := format(tt.namespace); got != tt.want { t.Errorf("format() = %v, want %v", got, tt.want) } }) } } ================================================ FILE: contrib/config/apollo/go.mod ================================================ module github.com/go-kratos/kratos/contrib/config/apollo/v2 go 1.22 require ( github.com/apolloconfig/agollo/v4 v4.3.1 github.com/go-kratos/kratos/v2 v2.9.2 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.11.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/config/apollo/go.sum ================================================ 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apolloconfig/agollo/v4 v4.3.1 h1:NHjd7KqOPmTvYwJidISc9MPBRO8m9UNrH3tijcEVNAY= github.com/apolloconfig/agollo/v4 v4.3.1/go.mod h1:n/7qxpKOTbegygLmO5OKmFWCdy3T+S/zioBGlo457Dk= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0= github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevid/gohamcrest v1.1.1 h1:ou+xSqlIw1xfGTg1uq1nif/htZ2S3EzRqLm2BP+tYU0= github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: contrib/config/apollo/parser.go ================================================ package apollo type jsonExtParser struct{} func (parser jsonExtParser) Parse(configContent any) (map[string]any, error) { return map[string]any{"content": configContent}, nil } type yamlExtParser struct{} func (parser yamlExtParser) Parse(configContent any) (map[string]any, error) { return map[string]any{"content": configContent}, nil } ================================================ FILE: contrib/config/apollo/watcher.go ================================================ package apollo import ( "context" "strings" "github.com/apolloconfig/agollo/v4/storage" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/log" ) type watcher struct { out <-chan []*config.KeyValue ctx context.Context cancelFn func() } type customChangeListener struct { in chan<- []*config.KeyValue apollo *apollo } func (c *customChangeListener) onChange(namespace string, changes map[string]*storage.ConfigChange) []*config.KeyValue { kv := make([]*config.KeyValue, 0, 2) if strings.Contains(namespace, ".") && !strings.HasSuffix(namespace, "."+properties) && (format(namespace) == yaml || format(namespace) == yml || format(namespace) == json) { if value, ok := changes["content"]; ok { kv = append(kv, &config.KeyValue{ Key: namespace, Value: []byte(value.NewValue.(string)), Format: format(namespace), }) return kv } } next := make(map[string]any) for key, change := range changes { resolve(genKey(namespace, key), change.NewValue, next) } f := format(namespace) codec := encoding.GetCodec(f) val, err := codec.Marshal(next) if err != nil { log.Warnf("apollo could not handle namespace %s: %v", namespace, err) return nil } kv = append(kv, &config.KeyValue{ Key: namespace, Value: val, Format: f, }) return kv } func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) { change := c.onChange(changeEvent.Namespace, changeEvent.Changes) if len(change) == 0 { return } c.in <- change } func (c *customChangeListener) OnNewestChange(_ *storage.FullChangeEvent) {} func newWatcher(a *apollo) (config.Watcher, error) { changeCh := make(chan []*config.KeyValue) listener := &customChangeListener{in: changeCh, apollo: a} a.client.AddChangeListener(listener) ctx, cancel := context.WithCancel(context.Background()) return &watcher{ out: changeCh, ctx: ctx, cancelFn: func() { a.client.RemoveChangeListener(listener) cancel() }, }, nil } // Next will be blocked until the Stop method is called func (w *watcher) Next() ([]*config.KeyValue, error) { select { case kv := <-w.out: return kv, nil case <-w.ctx.Done(): return nil, w.ctx.Err() } } func (w *watcher) Stop() error { if w.cancelFn != nil { w.cancelFn() } return nil } ================================================ FILE: contrib/config/apollo/watcher_test.go ================================================ package apollo import ( "testing" "github.com/apolloconfig/agollo/v4/storage" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/encoding" ) func Test_onChange(t *testing.T) { s := map[string]struct { Name string `yaml:"name"` }{ "app": { Name: "new", }, } codec := encoding.GetCodec(yaml) val, _ := codec.Marshal(s) c := customChangeListener{} tests := []struct { name string namespace string changes map[string]*storage.ConfigChange kvs []*config.KeyValue }{ { "test yaml onChange", "app.yaml", map[string]*storage.ConfigChange{ "name": { OldValue: "old", NewValue: "new", ChangeType: storage.MODIFIED, }, }, []*config.KeyValue{ { Key: "app.yaml", Value: val, Format: yaml, }, }, }, { "test json onChange", "app.json", map[string]*storage.ConfigChange{ "content": { OldValue: `{"name":"old"}`, NewValue: `{"name":"new"}`, ChangeType: storage.MODIFIED, }, }, []*config.KeyValue{ { Key: "app.json", Value: []byte(`{"name":"new"}`), Format: json, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { kvs := c.onChange(tt.namespace, tt.changes) if len(kvs) != len(tt.kvs) { t.Errorf("len(kvs) = %v, want %v", len(kvs), len(tt.kvs)) } for i := range kvs { if kvs[i].Format != tt.kvs[i].Format || kvs[i].Key != tt.kvs[i].Key || string(kvs[i].Value) != string(tt.kvs[i].Value) { t.Errorf("got %v, want %v", kvs[i], tt.kvs[i]) } } }) } } ================================================ FILE: contrib/config/consul/README.md ================================================ # Consul Config ```go import ( "github.com/hashicorp/consul/api" "github.com/go-kratos/kratos/contrib/config/consul/v2" ) func main() { consulClient, err := api.NewClient(&api.Config{ Address: "127.0.0.1:8500", }) if err != nil { panic(err) } cs, err := consul.New(consulClient, consul.WithPath("app/cart/configs/")) // consul中需要标注文件后缀,kratos读取配置需要适配文件后缀 // The file suffix needs to be marked, and kratos needs to adapt the file suffix to read the configuration. if err != nil { panic(err) } c := config.New(config.WithSource(cs)) } ``` ================================================ FILE: contrib/config/consul/config.go ================================================ package consul import ( "context" "errors" "path/filepath" "strings" "github.com/hashicorp/consul/api" "github.com/go-kratos/kratos/v2/config" ) // Option is consul config option. type Option func(o *options) type options struct { ctx context.Context path string } // WithContext with registry context. func WithContext(ctx context.Context) Option { return func(o *options) { o.ctx = ctx } } // WithPath is config path func WithPath(p string) Option { return func(o *options) { o.path = p } } type source struct { client *api.Client options *options } func New(client *api.Client, opts ...Option) (config.Source, error) { options := &options{ ctx: context.Background(), path: "", } for _, opt := range opts { opt(options) } if options.path == "" { return nil, errors.New("path invalid") } return &source{ client: client, options: options, }, nil } // Load return the config values func (s *source) Load() ([]*config.KeyValue, error) { kv, _, err := s.client.KV().List(s.options.path, nil) if err != nil { return nil, err } pathPrefix := s.options.path if !strings.HasSuffix(s.options.path, "/") { pathPrefix = pathPrefix + "/" } kvs := make([]*config.KeyValue, 0) for _, item := range kv { k := strings.TrimPrefix(item.Key, pathPrefix) if k == "" { continue } kvs = append(kvs, &config.KeyValue{ Key: k, Value: item.Value, Format: strings.TrimPrefix(filepath.Ext(k), "."), }) } return kvs, nil } // Watch return the watcher func (s *source) Watch() (config.Watcher, error) { return newWatcher(s) } ================================================ FILE: contrib/config/consul/config_test.go ================================================ package consul import ( "reflect" "testing" "time" "github.com/hashicorp/consul/api" "github.com/go-kratos/kratos/v2/config" ) const testPath = "kratos/test/config" const testKey = "kratos/test/config/key" func TestConfig(t *testing.T) { client, err := api.NewClient(&api.Config{ Address: "127.0.0.1:8500", }) if err != nil { t.Fatal(err) } if _, err = client.KV().Put(&api.KVPair{Key: testKey, Value: []byte("test config")}, nil); err != nil { t.Fatal(err) } source, err := New(client, WithPath(testPath)) if err != nil { t.Fatal(err) } kvs, err := source.Load() if err != nil { t.Fatal(err) } if len(kvs) != 1 || kvs[0].Key != "key" || string(kvs[0].Value) != "test config" { t.Fatal("config error") } w, err := source.Watch() if err != nil { t.Fatal(err) } defer func() { _ = w.Stop() }() if _, err = client.KV().Put(&api.KVPair{Key: testKey, Value: []byte("new config")}, nil); err != nil { t.Error(err) } if kvs, err = w.Next(); err != nil { t.Fatal(err) } if len(kvs) != 1 || kvs[0].Key != "key" || string(kvs[0].Value) != "new config" { t.Fatal("config error") } if _, err := client.KV().Delete(testKey, nil); err != nil { t.Error(err) } } func TestExtToFormat(t *testing.T) { client, err := api.NewClient(&api.Config{ Address: "127.0.0.1:8500", }) if err != nil { t.Fatal(err) } tp := "kratos/test/ext" tn := "a.bird.json" tk := tp + "/" + tn tc := `{"a":1}` if _, err = client.KV().Put(&api.KVPair{Key: tk, Value: []byte(tc)}, nil); err != nil { t.Fatal(err) } source, err := New(client, WithPath(tp)) if err != nil { t.Fatal(err) } kvs, err := source.Load() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(len(kvs), 1) { t.Errorf("len(kvs) is %d", len(kvs)) } if !reflect.DeepEqual(tn, kvs[0].Key) { t.Errorf("kvs[0].Key is %s", kvs[0].Key) } if !reflect.DeepEqual(tc, string(kvs[0].Value)) { t.Errorf("kvs[0].Value is %s", kvs[0].Value) } if !reflect.DeepEqual("json", kvs[0].Format) { t.Errorf("kvs[0].Format is %s", kvs[0].Format) } } func Test_source_Watch(t *testing.T) { client, err := api.NewClient(&api.Config{ Address: "127.0.0.1:8500", }) if err != nil { t.Fatal(err) } source, err := New(client, WithPath(testPath)) if err != nil { t.Fatal(err) } type fields struct { source config.Source } type args struct { key string value string } tests := []struct { name string fields fields args args want string wantErr bool deferFunc func(t *testing.T) }{ { name: "normal", fields: fields{source: source}, args: args{ key: testKey, value: "test value", }, want: "test value", wantErr: false, deferFunc: func(t *testing.T) { _, err := client.KV().Delete(testKey, nil) if err != nil { t.Error(err) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.deferFunc != nil { defer tt.deferFunc(t) } got, err := tt.fields.source.Watch() if (err != nil) != tt.wantErr { t.Errorf("Watch() error = %v, wantErr %v", err, tt.wantErr) return } time.Sleep(100 * time.Millisecond) _, err = client.KV().Put(&api.KVPair{Key: tt.args.key, Value: []byte(tt.args.value)}, nil) if err != nil { t.Error(err) } next, err := got.Next() if (err != nil) != tt.wantErr { t.Errorf("Watch() error = %v, wantErr %v", err, tt.wantErr) return } if len(next) != 1 { t.Error("watch is error") } if !reflect.DeepEqual(string(next[0].Value), tt.want) { t.Errorf("Watch got = %v, want %v", string(next[0].Value), tt.want) } }) } } func Test_source_Load(t *testing.T) { client, err := api.NewClient(&api.Config{ Address: "127.0.0.1:8500", }) if err != nil { t.Fatal(err) } source, err := New(client, WithPath(testPath)) if err != nil { t.Fatal(err) } type args struct { key string value string } type fields struct { source config.Source } tests := []struct { name string args args fields fields want []*config.KeyValue wantErr bool deferFunc func(t *testing.T) }{ { name: "normal", args: args{ key: testKey, value: "test value", }, fields: fields{ source: source, }, want: []*config.KeyValue{ { Key: "key", Value: []byte("test value"), }, }, deferFunc: func(t *testing.T) { _, err1 := client.KV().Delete(testKey, nil) if err1 != nil { t.Error(err) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.deferFunc != nil { defer tt.deferFunc(t) } _, err = client.KV().Put(&api.KVPair{Key: tt.args.key, Value: []byte(tt.args.value)}, nil) if err != nil { t.Error(err) } got, err := tt.fields.source.Load() if (err != nil) != tt.wantErr { t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got[0], tt.want[0]) { t.Errorf("Load() got = %v, want %v", got, tt.want) } }) } } ================================================ FILE: contrib/config/consul/go.mod ================================================ module github.com/go-kratos/kratos/contrib/config/consul/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/hashicorp/consul/api v1.26.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/fatih/color v1.14.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/sys v0.28.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/config/consul/go.sum ================================================ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/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-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/config/consul/watcher.go ================================================ package consul import ( "context" "path/filepath" "strings" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api/watch" "github.com/go-kratos/kratos/v2/config" ) type watcher struct { source *source ch chan []*config.KeyValue wp *watch.Plan fileModifyIndex map[string]uint64 ctx context.Context cancel context.CancelFunc } func (w *watcher) handle(_ uint64, data any) { if data == nil { return } kv, ok := data.(api.KVPairs) if !ok { return } pathPrefix := w.source.options.path if !strings.HasSuffix(w.source.options.path, "/") { pathPrefix = pathPrefix + "/" } kvs := make([]*config.KeyValue, 0, len(kv)) for _, item := range kv { if index, ok := w.fileModifyIndex[item.Key]; ok && item.ModifyIndex == index { continue } k := strings.TrimPrefix(item.Key, pathPrefix) if k == "" { continue } kvs = append(kvs, &config.KeyValue{ Key: k, Value: item.Value, Format: strings.TrimPrefix(filepath.Ext(k), "."), }) w.fileModifyIndex[item.Key] = item.ModifyIndex } if len(kvs) == 0 { return } w.ch <- kvs } func newWatcher(s *source) (*watcher, error) { ctx, cancel := context.WithCancel(context.Background()) w := &watcher{ source: s, ch: make(chan []*config.KeyValue), fileModifyIndex: make(map[string]uint64), ctx: ctx, cancel: cancel, } wp, err := watch.Parse(map[string]any{"type": "keyprefix", "prefix": s.options.path}) if err != nil { return nil, err } wp.Handler = w.handle w.wp = wp // wp.Run is a blocking call and will prevent newWatcher from returning go func() { err := wp.RunWithClientAndHclog(s.client, nil) if err != nil { panic(err) } }() return w, nil } func (w *watcher) Next() ([]*config.KeyValue, error) { select { case kv := <-w.ch: return kv, nil case <-w.ctx.Done(): return nil, w.ctx.Err() } } func (w *watcher) Stop() error { w.wp.Stop() w.cancel() return nil } ================================================ FILE: contrib/config/etcd/README.md ================================================ # Etcd Config ```go import ( "log" clientv3 "go.etcd.io/etcd/client/v3" "google.golang.org/grpc" cfg "github.com/go-kratos/kratos/contrib/config/etcd/v2" "github.com/go-kratos/kratos/v2/config" ) // create an etcd client client, err := clientv3.New(clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, DialTimeout: time.Second, DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) if err != nil { log.Fatal(err) } // configure the source, "path" is required source, err := cfg.New(client, cfg.WithPath("/app-config"), cfg.WithPrefix(true)) if err != nil { log.Fatalln(err) } // create a config instance with source c := config.New(config.WithSource(source)) defer c.Close() // load sources before get if err := c.Load(); err != nil { log.Fatalln(err) } // acquire config value foo, err := c.Value("/app-config").String() if err != nil { log.Fatalln(err) } log.Println(foo) ``` ================================================ FILE: contrib/config/etcd/config.go ================================================ package etcd import ( "context" "errors" "path/filepath" "strings" clientv3 "go.etcd.io/etcd/client/v3" "github.com/go-kratos/kratos/v2/config" ) // Option is etcd config option. type Option func(o *options) type options struct { ctx context.Context path string prefix bool } // WithContext with registry context. func WithContext(ctx context.Context) Option { return func(o *options) { o.ctx = ctx } } // WithPath is config path func WithPath(p string) Option { return func(o *options) { o.path = p } } // WithPrefix is config prefix func WithPrefix(prefix bool) Option { return func(o *options) { o.prefix = prefix } } type source struct { client *clientv3.Client options *options } func New(client *clientv3.Client, opts ...Option) (config.Source, error) { options := &options{ ctx: context.Background(), path: "", prefix: false, } for _, opt := range opts { opt(options) } if options.path == "" { return nil, errors.New("path invalid") } return &source{ client: client, options: options, }, nil } // Load return the config values func (s *source) Load() ([]*config.KeyValue, error) { var opts []clientv3.OpOption if s.options.prefix { opts = append(opts, clientv3.WithPrefix()) } rsp, err := s.client.Get(s.options.ctx, s.options.path, opts...) if err != nil { return nil, err } kvs := make([]*config.KeyValue, 0, len(rsp.Kvs)) for _, item := range rsp.Kvs { k := string(item.Key) kvs = append(kvs, &config.KeyValue{ Key: k, Value: item.Value, Format: strings.TrimPrefix(filepath.Ext(k), "."), }) } return kvs, nil } // Watch return the watcher func (s *source) Watch() (config.Watcher, error) { return newWatcher(s), nil } ================================================ FILE: contrib/config/etcd/config_test.go ================================================ package etcd import ( "context" "reflect" "testing" "time" clientv3 "go.etcd.io/etcd/client/v3" "google.golang.org/grpc" ) const testKey = "/kratos/test/config" func TestConfig(t *testing.T) { client, err := clientv3.New(clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, DialTimeout: time.Second, DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) if err != nil { t.Fatal(err) } defer func() { _ = client.Close() }() if _, err = client.Put(context.Background(), testKey, "test config"); err != nil { t.Fatal(err) } source, err := New(client, WithPath(testKey)) if err != nil { t.Fatal(err) } kvs, err := source.Load() if err != nil { t.Fatal(err) } if len(kvs) != 1 || kvs[0].Key != testKey || string(kvs[0].Value) != "test config" { t.Fatal("config error") } w, err := source.Watch() if err != nil { t.Fatal(err) } defer func() { _ = w.Stop() }() if _, err = client.Put(context.Background(), testKey, "new config"); err != nil { t.Error(err) } if kvs, err = w.Next(); err != nil { t.Fatal(err) } if len(kvs) != 1 || kvs[0].Key != testKey || string(kvs[0].Value) != "new config" { t.Fatal("config error") } if _, err := client.Delete(context.Background(), testKey); err != nil { t.Error(err) } } func TestExtToFormat(t *testing.T) { client, err := clientv3.New(clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, DialTimeout: time.Second, DialOptions: []grpc.DialOption{grpc.WithBlock()}, }) if err != nil { t.Fatal(err) } defer func() { _ = client.Close() }() tp := "/kratos/test/ext" tn := "a.bird.json" tk := tp + "/" + tn tc := `{"a":1}` if _, err = client.Put(context.Background(), tk, tc); err != nil { t.Fatal(err) } source, err := New(client, WithPath(tp), WithPrefix(true)) if err != nil { t.Fatal(err) } kvs, err := source.Load() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(len(kvs), 1) { t.Errorf("len(kvs) = %d", len(kvs)) } if !reflect.DeepEqual(tk, kvs[0].Key) { t.Errorf("kvs[0].Key is %s", kvs[0].Key) } if !reflect.DeepEqual(tc, string(kvs[0].Value)) { t.Errorf("kvs[0].Value is %s", kvs[0].Value) } if !reflect.DeepEqual("json", kvs[0].Format) { t.Errorf("kvs[0].Format is %s", kvs[0].Format) } } func TestEtcdWithPath(t *testing.T) { tests := []struct { name string fields string want string }{ { name: "default", fields: testKey, want: testKey, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { options := &options{ ctx: context.Background(), } got := WithPath(tt.fields) got(options) if options.path != tt.want { t.Errorf("WithPath(tt.fields) = %v, want %v", got, tt.want) } }) } } func TestEtcdWithPrefix(t *testing.T) { tests := []struct { name string fields bool want bool }{ { name: "default", fields: false, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { options := &options{ ctx: context.Background(), } got := WithPrefix(tt.fields) got(options) if options.prefix != tt.want { t.Errorf("WithPrefix(tt.fields) = %v, want %v", got, tt.want) } }) } } ================================================ FILE: contrib/config/etcd/go.mod ================================================ module github.com/go-kratos/kratos/contrib/config/etcd/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 go.etcd.io/etcd/client/v3 v3.5.11 google.golang.org/grpc v1.61.1 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/kr/text v0.2.0 // indirect go.etcd.io/etcd/api/v3 v3.5.11 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/config/etcd/go.sum ================================================ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/etcd/api/v3 v3.5.11 h1:B54KwXbWDHyD3XYAwprxNzTe7vlhR69LuBgZnMVvS7E= go.etcd.io/etcd/api/v3 v3.5.11/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= go.etcd.io/etcd/client/pkg/v3 v3.5.11 h1:bT2xVspdiCj2910T0V+/KHcVKjkUrCZVtk8J2JF2z1A= go.etcd.io/etcd/client/pkg/v3 v3.5.11/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= go.etcd.io/etcd/client/v3 v3.5.11 h1:ajWtgoNSZJ1gmS8k+icvPtqsqEav+iUorF7b0qozgUU= go.etcd.io/etcd/client/v3 v3.5.11/go.mod h1:a6xQUEqFJ8vztO1agJh/KQKOMfFI8og52ZconzcDJwE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/config/etcd/watcher.go ================================================ package etcd import ( "context" clientv3 "go.etcd.io/etcd/client/v3" "github.com/go-kratos/kratos/v2/config" ) type watcher struct { source *source ch clientv3.WatchChan ctx context.Context cancel context.CancelFunc } func newWatcher(s *source) *watcher { ctx, cancel := context.WithCancel(context.Background()) w := &watcher{ source: s, ctx: ctx, cancel: cancel, } var opts []clientv3.OpOption if s.options.prefix { opts = append(opts, clientv3.WithPrefix()) } w.ch = s.client.Watch(s.options.ctx, s.options.path, opts...) return w } func (w *watcher) Next() ([]*config.KeyValue, error) { select { case resp := <-w.ch: if err := resp.Err(); err != nil { return nil, err } return w.source.Load() case <-w.ctx.Done(): return nil, w.ctx.Err() } } func (w *watcher) Stop() error { w.cancel() return nil } ================================================ FILE: contrib/config/kubernetes/README.md ================================================ # Kubernetes Config ### Usage in the Kubernetes Cluster It is required to > serviceaccount should be set to the actual account of your environment, the default account will be `namespace::default` if the `spec.serviceAccount` is unset. execute this command: ``` kubectl create clusterrolebinding go-kratos:kube --clusterrole=view --serviceaccount=mesh:default ``` or use `kubectl apply -f bind-role.yaml` ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: go-kratos:kube roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: view subjects: - kind: ServiceAccount name: default namespace: mesh ``` ### Usage outside the Kubernetes Cluster Set the path `~/.kube/config` to KubeConfig ```go config.NewSource(SourceOption{ Namespace: "mesh", LabelSelector: "", KubeConfig: filepath.Join(homedir.HomeDir(), ".kube", "config"), }) ``` ================================================ FILE: contrib/config/kubernetes/config.go ================================================ package kubernetes import ( "context" "errors" "fmt" "path/filepath" "strings" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "github.com/go-kratos/kratos/v2/config" ) // Option is kubernetes option. type Option func(*options) type options struct { // kubernetes namespace Namespace string // kubernetes labelSelector example `app=test` LabelSelector string // kubernetes fieldSelector example `app=test` FieldSelector string // set KubeConfig out-of-cluster Use outside cluster KubeConfig string // set master url Master string } // Namespace with kubernetes namespace. func Namespace(ns string) Option { return func(o *options) { o.Namespace = ns } } // LabelSelector with kubernetes label selector. func LabelSelector(label string) Option { return func(o *options) { o.LabelSelector = label } } // FieldSelector with kubernetes field selector. func FieldSelector(field string) Option { return func(o *options) { o.FieldSelector = field } } // KubeConfig with kubernetes config. func KubeConfig(config string) Option { return func(o *options) { o.KubeConfig = config } } // Master with kubernetes master. func Master(master string) Option { return func(o *options) { o.Master = master } } type kube struct { opts options client *kubernetes.Clientset } // NewSource new a kubernetes config source. func NewSource(opts ...Option) config.Source { op := options{} for _, o := range opts { o(&op) } return &kube{ opts: op, } } func (k *kube) init() (err error) { var config *rest.Config if k.opts.KubeConfig != "" { if config, err = clientcmd.BuildConfigFromFlags(k.opts.Master, k.opts.KubeConfig); err != nil { return err } } else { if config, err = rest.InClusterConfig(); err != nil { return err } } if k.client, err = kubernetes.NewForConfig(config); err != nil { return err } return nil } func (k *kube) load() (kvs []*config.KeyValue, err error) { cmList, err := k.client. CoreV1(). ConfigMaps(k.opts.Namespace). List(context.Background(), metav1.ListOptions{ LabelSelector: k.opts.LabelSelector, FieldSelector: k.opts.FieldSelector, }) if err != nil { return nil, err } for _, cm := range cmList.Items { kvs = append(kvs, k.configMap(cm)...) } return kvs, nil } func (k *kube) configMap(cm v1.ConfigMap) (kvs []*config.KeyValue) { for name, val := range cm.Data { k := fmt.Sprintf("%s/%s/%s", k.opts.Namespace, cm.Name, name) kvs = append(kvs, &config.KeyValue{ Key: k, Value: []byte(val), Format: strings.TrimPrefix(filepath.Ext(k), "."), }) } return kvs } func (k *kube) Load() ([]*config.KeyValue, error) { if k.opts.Namespace == "" { return nil, errors.New("options namespace not full") } if err := k.init(); err != nil { return nil, err } return k.load() } func (k *kube) Watch() (config.Watcher, error) { return newWatcher(k) } ================================================ FILE: contrib/config/kubernetes/config_test.go ================================================ package kubernetes import ( "context" "log" "path/filepath" "reflect" "strings" "testing" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" "github.com/go-kratos/kratos/v2/config" ) const ( testKey = "test_config.json" namespace = "default" name = "test" ) var ( keyPath = strings.Join([]string{namespace, name, testKey}, "/") objectMeta = metav1.ObjectMeta{ Name: name, Namespace: namespace, Labels: map[string]string{ "app": "test", }, } ) func TestSource(t *testing.T) { home := homedir.HomeDir() s := NewSource( Namespace("default"), LabelSelector(""), KubeConfig(filepath.Join(home, ".kube", "config")), ) kvs, err := s.Load() if err != nil { t.Error(err) } for _, v := range kvs { t.Log(v) } } func ExampleNewSource() { conf := config.New( config.WithSource( NewSource( Namespace("mesh"), LabelSelector("app=test"), KubeConfig(filepath.Join(homedir.HomeDir(), ".kube", "config")), ), ), ) err := conf.Load() if err != nil { log.Panic(err) } } func TestConfig(t *testing.T) { restConfig, err := rest.InClusterConfig() home := homedir.HomeDir() options := []Option{ Namespace(namespace), LabelSelector("app=test"), } if err != nil { kubeconfig := filepath.Join(home, ".kube", "config") restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { t.Fatal(err) } options = append(options, KubeConfig(kubeconfig)) } clientSet, err := kubernetes.NewForConfig(restConfig) if err != nil { t.Fatal(err) } clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace) source := NewSource(options...) if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{ ObjectMeta: objectMeta, Data: map[string]string{ testKey: "test config", }, }, metav1.CreateOptions{}); err != nil { t.Fatal(err) } defer func() { if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil { t.Error(err) } }() kvs, err := source.Load() if err != nil { t.Fatal(err) } if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "test config" { t.Fatal("config error") } w, err := source.Watch() if err != nil { t.Fatal(err) } defer func() { _ = w.Stop() }() // create also produce an event, discard it if _, err = w.Next(); err != nil { t.Fatal(err) } if _, err = clientSetConfigMaps.Update(context.Background(), &v1.ConfigMap{ ObjectMeta: objectMeta, Data: map[string]string{ testKey: "new config", }, }, metav1.UpdateOptions{}); err != nil { t.Error(err) } if kvs, err = w.Next(); err != nil { t.Fatal(err) } if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "new config" { t.Fatal("config error") } } func TestExtToFormat(t *testing.T) { restConfig, err := rest.InClusterConfig() home := homedir.HomeDir() options := []Option{ Namespace(namespace), LabelSelector("app=test"), } if err != nil { kubeconfig := filepath.Join(home, ".kube", "config") restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { t.Fatal(err) } options = append(options, KubeConfig(kubeconfig)) } clientSet, err := kubernetes.NewForConfig(restConfig) if err != nil { t.Fatal(err) } clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace) tc := `{"a":1}` if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{ ObjectMeta: objectMeta, Data: map[string]string{ testKey: tc, }, }, metav1.CreateOptions{}); err != nil { t.Fatal(err) } defer func() { if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil { t.Error(err) } }() source := NewSource(options...) if err != nil { t.Fatal(err) } kvs, err := source.Load() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(len(kvs), 1) { t.Errorf("len(kvs) = %d", len(kvs)) } if !reflect.DeepEqual(keyPath, kvs[0].Key) { t.Errorf("kvs[0].Key is %s", kvs[0].Key) } if !reflect.DeepEqual(tc, string(kvs[0].Value)) { t.Errorf("kvs[0].Value is %s", kvs[0].Value) } if !reflect.DeepEqual("json", kvs[0].Format) { t.Errorf("kvs[0].Format is %s", kvs[0].Format) } } ================================================ FILE: contrib/config/kubernetes/go.mod ================================================ module github.com/go-kratos/kratos/contrib/config/kubernetes/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 k8s.io/client-go v0.26.3 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/config/kubernetes/go.sum ================================================ 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 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.4.1/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.1/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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= ================================================ FILE: contrib/config/kubernetes/watcher.go ================================================ package kubernetes import ( "context" "fmt" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" "github.com/go-kratos/kratos/v2/config" ) type watcher struct { k *kube watcher watch.Interface } func newWatcher(k *kube) (config.Watcher, error) { w, err := k.client.CoreV1().ConfigMaps(k.opts.Namespace).Watch(context.Background(), metav1.ListOptions{ LabelSelector: k.opts.LabelSelector, FieldSelector: k.opts.FieldSelector, }) if err != nil { return nil, err } return &watcher{ k: k, watcher: w, }, nil } func (w *watcher) Next() ([]*config.KeyValue, error) { ResultChan: ch := <-w.watcher.ResultChan() if ch.Object == nil { // recreate the watcher k8sWatcher, err := w.k.client.CoreV1().ConfigMaps(w.k.opts.Namespace).Watch(context.Background(), metav1.ListOptions{ LabelSelector: w.k.opts.LabelSelector, FieldSelector: w.k.opts.FieldSelector, }) if err != nil { return nil, err } w.watcher = k8sWatcher goto ResultChan } cm, ok := ch.Object.(*v1.ConfigMap) if !ok { return nil, fmt.Errorf("kubernetes Object not ConfigMap") } if ch.Type == "DELETED" { return nil, fmt.Errorf("kubernetes configmap delete %s", cm.Name) } return w.k.configMap(*cm), nil } func (w *watcher) Stop() error { w.watcher.Stop() return nil } ================================================ FILE: contrib/config/kubernetes/watcher_test.go ================================================ package kubernetes import ( "context" "path/filepath" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) func TestKube(t *testing.T) { home := homedir.HomeDir() config, err := clientcmd.BuildConfigFromFlags("", filepath.Join(home, ".kube", "config")) if err != nil { t.Error(err) } client, err := kubernetes.NewForConfig(config) if err != nil { t.Error(err) } cmWatcher, err := client.CoreV1().ConfigMaps("mesh").Watch(context.Background(), metav1.ListOptions{ LabelSelector: "app=test", // FieldSelector: "", }) if err != nil { t.Error(err) } go func() { time.Sleep(5 * time.Second) cmWatcher.Stop() }() for c := range cmWatcher.ResultChan() { if c.Object == nil { return } t.Log(c.Type, c.Object) } } ================================================ FILE: contrib/config/nacos/README.md ================================================ # Nacos Config ```go import ( "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/common/constant" kconfig "github.com/go-kratos/kratos/v2/config" ) sc := []constant.ServerConfig{ *constant.NewServerConfig("127.0.0.1", 8848), } cc := &constant.ClientConfig{ NamespaceId: "public", //namespace id TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } // a more graceful way to create naming client client, err := clients.NewConfigClient( vo.NacosClientParam{ ClientConfig: cc, ServerConfigs: sc, }, ) if err != nil { log.Panic(err) } ``` ================================================ FILE: contrib/config/nacos/config.go ================================================ package config import ( "context" "path/filepath" "strings" "github.com/nacos-group/nacos-sdk-go/clients/config_client" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/v2/config" ) type Option func(*options) type options struct { group string dataID string } // WithGroup With nacos config group. func WithGroup(group string) Option { return func(o *options) { o.group = group } } // WithDataID With nacos config data id. func WithDataID(dataID string) Option { return func(o *options) { o.dataID = dataID } } type Config struct { opts options client config_client.IConfigClient } func NewConfigSource(client config_client.IConfigClient, opts ...Option) config.Source { _options := options{} for _, o := range opts { o(&_options) } return &Config{client: client, opts: _options} } func (c *Config) Load() ([]*config.KeyValue, error) { content, err := c.client.GetConfig(vo.ConfigParam{ DataId: c.opts.dataID, Group: c.opts.group, }) if err != nil { return nil, err } k := c.opts.dataID return []*config.KeyValue{ { Key: k, Value: []byte(content), Format: strings.TrimPrefix(filepath.Ext(k), "."), }, }, nil } func (c *Config) Watch() (config.Watcher, error) { watcher := newWatcher(context.Background(), c.opts.dataID, c.opts.group, c.client.CancelListenConfig) err := c.client.ListenConfig(vo.ConfigParam{ DataId: c.opts.dataID, Group: c.opts.group, OnChange: func(_, group, dataId, data string) { if dataId == watcher.dataID && group == watcher.group { watcher.content <- data } }, }) if err != nil { return nil, err } return watcher, nil } ================================================ FILE: contrib/config/nacos/config_test.go ================================================ package config import ( "reflect" "testing" "time" "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/v2/config" ) func TestConfig_Load(t *testing.T) { sc := []constant.ServerConfig{ *constant.NewServerConfig("127.0.0.1", 8848), } cc := constant.ClientConfig{ TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } client, err := clients.NewConfigClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } source := NewConfigSource(client, WithGroup("test"), WithDataID("test.yaml")) type fields struct { source config.Source } tests := []struct { name string fields fields want []*config.KeyValue wantErr bool preFunc func(t *testing.T) deferFunc func(t *testing.T) }{ { name: "normal", fields: fields{ source: source, }, wantErr: false, preFunc: func(t *testing.T) { _, err = client.PublishConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test", Content: "test: test"}) if err != nil { t.Error(err) } time.Sleep(time.Second * 1) }, deferFunc: func(t *testing.T) { _, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test"}) if dErr != nil { t.Error(dErr) } }, want: []*config.KeyValue{{ Key: "test.yaml", Value: []byte("test: test"), Format: "yaml", }}, }, { name: "error", fields: fields{ source: source, }, wantErr: false, preFunc: func(t *testing.T) { _, err = client.PublishConfig(vo.ConfigParam{DataId: "111.yaml", Group: "notExist", Content: "test: test"}) if err != nil { t.Error(err) } time.Sleep(time.Second * 1) }, deferFunc: func(t *testing.T) { _, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "111.yaml", Group: "notExist"}) if dErr != nil { t.Error(dErr) } }, want: []*config.KeyValue{{ Key: "test.yaml", Value: []byte{}, Format: "yaml", }}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.preFunc != nil { test.preFunc(t) } if test.deferFunc != nil { defer test.deferFunc(t) } s := test.fields.source configs, lErr := s.Load() if (lErr != nil) != test.wantErr { t.Errorf("Load error = %v, wantErr %v", lErr, test.wantErr) t.Errorf("Load configs = %v", configs) return } if !reflect.DeepEqual(configs, test.want) { t.Errorf("Load configs = %v, want %v", configs, test.want) } }) } } func TestConfig_Watch(t *testing.T) { sc := []constant.ServerConfig{ *constant.NewServerConfig("127.0.0.1", 8848), } cc := constant.ClientConfig{ TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } client, err := clients.NewConfigClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } source := NewConfigSource(client, WithGroup("test"), WithDataID("test.yaml")) type fields struct { source config.Source } tests := []struct { name string fields fields want []*config.KeyValue wantErr bool processFunc func(t *testing.T, w config.Watcher) deferFunc func(t *testing.T, w config.Watcher) }{ { name: "normal", fields: fields{ source: source, }, wantErr: false, processFunc: func(t *testing.T, _ config.Watcher) { _, pErr := client.PublishConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test", Content: "test: test"}) if pErr != nil { t.Error(pErr) } }, deferFunc: func(t *testing.T, _ config.Watcher) { _, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test"}) if dErr != nil { t.Error(dErr) } }, want: []*config.KeyValue{{ Key: "test.yaml", Value: []byte("test: test"), Format: "yaml", }}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { s := test.fields.source watch, wErr := s.Watch() if wErr != nil { t.Error(wErr) return } if test.processFunc != nil { test.processFunc(t, watch) } if test.deferFunc != nil { defer test.deferFunc(t, watch) } want, nErr := watch.Next() if (nErr != nil) != test.wantErr { t.Errorf("Watch error = %v, wantErr %v", nErr, test.wantErr) return } if !reflect.DeepEqual(want, test.want) { t.Errorf("Watch watcher = %v, want %v", watch, test.want) } }) } } ================================================ FILE: contrib/config/nacos/go.mod ================================================ module github.com/go-kratos/kratos/contrib/config/nacos/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/nacos-group/nacos-sdk-go v1.0.9 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/json-iterator/go v1.1.6 // indirect github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect go.uber.org/atomic v1.6.0 // indirect go.uber.org/multierr v1.5.0 // indirect go.uber.org/zap v1.15.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.42.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/config/nacos/go.sum ================================================ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 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/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 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/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4= github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nacos-group/nacos-sdk-go v1.0.9 h1:sMvrp6tZj4LdhuHRsS4GCqASB81k3pjmT2ykDQQpwt0= github.com/nacos-group/nacos-sdk-go v1.0.9/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= ================================================ FILE: contrib/config/nacos/watcher.go ================================================ package config import ( "context" "path/filepath" "strings" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/v2/config" ) type Watcher struct { dataID string group string content chan string cancelListenConfig cancelListenConfigFunc ctx context.Context cancel context.CancelFunc } type cancelListenConfigFunc func(params vo.ConfigParam) (err error) func newWatcher(ctx context.Context, dataID string, group string, cancelListenConfig cancelListenConfigFunc) *Watcher { ctx, cancel := context.WithCancel(ctx) w := &Watcher{ dataID: dataID, group: group, cancelListenConfig: cancelListenConfig, content: make(chan string, 100), ctx: ctx, cancel: cancel, } return w } func (w *Watcher) Next() ([]*config.KeyValue, error) { select { case <-w.ctx.Done(): return nil, w.ctx.Err() case content := <-w.content: k := w.dataID return []*config.KeyValue{ { Key: k, Value: []byte(content), Format: strings.TrimPrefix(filepath.Ext(k), "."), }, }, nil } } func (w *Watcher) Close() error { err := w.cancelListenConfig(vo.ConfigParam{ DataId: w.dataID, Group: w.group, }) w.cancel() return err } func (w *Watcher) Stop() error { return w.Close() } ================================================ FILE: contrib/config/polaris/README.md ================================================ # Polaris Config ```go import ( "log" "github.com/polarismesh/polaris-go" "github.com/go-kratos/kratos/contrib/config/polaris/v2" ) func main() { configApi, err := polaris.NewConfigAPI() if err != nil { log.Fatalln(err) } source, err := New(&configApi, WithNamespace("default"), WithFileGroup("default"), WithFileName("default.yaml")) if err != nil { log.Fatalln(err) } source.Load() } ``` ================================================ FILE: contrib/config/polaris/config.go ================================================ package config import ( "errors" "fmt" "path/filepath" "strings" "github.com/polarismesh/polaris-go" "github.com/go-kratos/kratos/v2/config" ) // Option is polaris config option. type Option func(o *options) type options struct { namespace string fileGroup string fileName string configFile polaris.ConfigFile } // WithNamespace with polaris config namespace func WithNamespace(namespace string) Option { return func(o *options) { o.namespace = namespace } } // WithFileGroup with polaris config fileGroup func WithFileGroup(fileGroup string) Option { return func(o *options) { o.fileGroup = fileGroup } } // WithFileName with polaris config fileName func WithFileName(fileName string) Option { return func(o *options) { o.fileName = fileName } } type source struct { client polaris.ConfigAPI options *options } func New(client polaris.ConfigAPI, opts ...Option) (config.Source, error) { options := &options{ namespace: "default", fileGroup: "", fileName: "", } for _, opt := range opts { opt(options) } if options.fileGroup == "" { return nil, errors.New("fileGroup invalid") } if options.fileName == "" { return nil, errors.New("fileName invalid") } return &source{ client: client, options: options, }, nil } // Load return the config values func (s *source) Load() ([]*config.KeyValue, error) { configFile, err := s.client.GetConfigFile(s.options.namespace, s.options.fileGroup, s.options.fileName) if err != nil { fmt.Println("fail to get config.", err) return nil, err } content := configFile.GetContent() k := s.options.fileName s.options.configFile = configFile return []*config.KeyValue{ { Key: k, Value: []byte(content), Format: strings.TrimPrefix(filepath.Ext(k), "."), }, }, nil } // Watch return the watcher func (s *source) Watch() (config.Watcher, error) { return newWatcher(s.options.configFile), nil } ================================================ FILE: contrib/config/polaris/go.mod ================================================ module github.com/go-kratos/kratos/contrib/config/polaris/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/polarismesh/polaris-go v1.1.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.35.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/config/polaris/go.sum ================================================ 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/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/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/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.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/polarismesh/polaris-go v1.1.0 h1:nFvn3q3XaVFhzF7pBnIySrN0ZZBwvbbYXC5r2DpsQN0= github.com/polarismesh/polaris-go v1.1.0/go.mod h1:tquawfjEKp1W3ffNJQSzhfditjjoZ7tvhOCElN7Efzs= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181114220301-adae6a3d119a/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: contrib/config/polaris/polaris.yaml ================================================ global: serverConnector: addresses: - 127.0.0.1:8091 config: configConnector: addresses: - 127.0.0.1:8093 ================================================ FILE: contrib/config/polaris/watcher.go ================================================ package config import ( "fmt" "path/filepath" "strings" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/model" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/log" ) type Watcher struct { configFile polaris.ConfigFile fullPath string } type eventChan struct { closed bool event chan model.ConfigFileChangeEvent } var eventChanMap = make(map[string]eventChan) func getFullPath(namespace string, fileGroup string, fileName string) string { return fmt.Sprintf("%s/%s/%s", namespace, fileGroup, fileName) } func receive(event model.ConfigFileChangeEvent) { meta := event.ConfigFileMetadata ec := eventChanMap[getFullPath(meta.GetNamespace(), meta.GetFileGroup(), meta.GetFileName())] defer func() { if err := recover(); err != nil { log.Error(err) } }() if !ec.closed { ec.event <- event } } func newWatcher(configFile polaris.ConfigFile) *Watcher { configFile.AddChangeListener(receive) fullPath := getFullPath(configFile.GetNamespace(), configFile.GetFileGroup(), configFile.GetFileName()) if _, ok := eventChanMap[fullPath]; !ok { eventChanMap[fullPath] = eventChan{ closed: false, event: make(chan model.ConfigFileChangeEvent), } } w := &Watcher{ configFile: configFile, fullPath: fullPath, } return w } func (w *Watcher) Next() ([]*config.KeyValue, error) { ec := eventChanMap[w.fullPath] event := <-ec.event return []*config.KeyValue{ { Key: w.configFile.GetFileName(), Value: []byte(event.NewValue), Format: strings.TrimPrefix(filepath.Ext(w.configFile.GetFileName()), "."), }, }, nil } func (w *Watcher) Stop() error { ec := eventChanMap[w.fullPath] if !ec.closed { ec.closed = true close(ec.event) } return nil } ================================================ FILE: contrib/encoding/msgpack/go.mod ================================================ module github.com/go-kratos/kratos/contrib/encoding/msgpack/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/vmihailenco/msgpack/v5 v5.4.1 ) require github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/encoding/msgpack/go.sum ================================================ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/encoding/msgpack/msgpack.go ================================================ package msgpack import ( "github.com/vmihailenco/msgpack/v5" "github.com/go-kratos/kratos/v2/encoding" ) // Name is the name registered for the msgpack compressor. const Name = "msgpack" func init() { encoding.RegisterCodec(codec{}) } // codec is a Codec implementation with msgpack. type codec struct{} func (codec) Marshal(v any) ([]byte, error) { return msgpack.Marshal(v) } func (codec) Unmarshal(data []byte, v any) error { return msgpack.Unmarshal(data, v) } func (codec) Name() string { return Name } ================================================ FILE: contrib/encoding/msgpack/msgpack_test.go ================================================ package msgpack import ( "reflect" "testing" ) type loginRequest struct { UserName string Password string } type testModel struct { ID int32 Name string } func TestName(t *testing.T) { c := new(codec) if !reflect.DeepEqual("msgpack", c.Name()) { t.Errorf("Name() should be msgpack, but got %s", c.Name()) } } func TestCodec(t *testing.T) { c := new(codec) t2 := testModel{ID: 1, Name: "name"} m, err := c.Marshal(&t2) if err != nil { t.Errorf("Marshal() should be nil, but got %s", err) } var t3 testModel err = c.Unmarshal(m, &t3) if err != nil { t.Errorf("Unmarshal() should be nil, but got %s", err) } if !reflect.DeepEqual(t2.ID, t3.ID) { t.Errorf("ID should be %d, but got %d", t2.ID, t3.ID) } if !reflect.DeepEqual(t3.Name, t2.Name) { t.Errorf("Name should be %s, but got %s", t2.Name, t3.Name) } request := loginRequest{ UserName: "username", Password: "password", } m, err = c.Marshal(&request) if err != nil { t.Errorf("Marshal() should be nil, but got %s", err) } var req loginRequest err = c.Unmarshal(m, &req) if err != nil { t.Errorf("Unmarshal() should be nil, but got %s", err) } if !reflect.DeepEqual(req.Password, request.Password) { t.Errorf("ID should be %s, but got %s", req.Password, request.Password) } if !reflect.DeepEqual(req.UserName, request.UserName) { t.Errorf("Name should be %s, but got %s", req.UserName, request.UserName) } } ================================================ FILE: contrib/errortracker/sentry/README.md ================================================ # Sentry middleware for Kratos This middleware helps you to catch panics and report them to [sentry](https://sentry.io/) ## Quick Start You could check the full demo in example folder. ```go // Step 1: // init sentry in the entry of your application import "github.com/getsentry/sentry-go" sentry.Init(sentry.ClientOptions{ Dsn: "", AttachStacktrace: true, // recommended }) // Step 2: // set middleware import ksentry "github.com/go-kratos/kratos/contrib/errortracker/sentry/v2" // for HTTP server, new HTTP server with sentry middleware options var opts = []http.ServerOption{ http.Middleware( recovery.Recovery(), tracing.Server(), ksentry.Server(ksentry.WithTags(map[string]interface{}{ "tag": "some-custom-constant-tag", "trace_id": tracing.TraceID(), // If you want to use the TraceID valuer, you need to place it after the A middleware. })), // must after Recovery middleware, because of the exiting order will be reversed logging.Server(logger), ), } // for gRPC server, new gRPC server with sentry middleware options var opts = []grpc.ServerOption{ grpc.Middleware( recovery.Recovery(), tracing.Server(), ksentry.Server(ksentry.WithTags(map[string]interface{}{ "tag": "some-custom-constant-tag", "trace_id": tracing.TraceID(), // If you want to use the TraceID valuer, you need to place it after the A middleware. })), // must after Recovery middleware, because of the exiting order will be reversed logging.Server(logger), ), } // Then, the framework will report events to Sentry when your trigger panics. // Or your can push events to Sentry manually ``` ## Reference * [https://docs.sentry.io/platforms/go/](https://docs.sentry.io/platforms/go/) ================================================ FILE: contrib/errortracker/sentry/go.mod ================================================ module github.com/go-kratos/kratos/contrib/errortracker/sentry/v2 go 1.22 require ( github.com/getsentry/sentry-go v0.25.0 github.com/go-kratos/kratos/v2 v2.9.2 ) require ( github.com/go-kratos/aegis v0.2.0 // indirect github.com/go-playground/form/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.4.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/kr/text v0.2.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/errortracker/sentry/go.sum ================================================ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb h1:kxNVXsNro/lpR5WD+P1FI/yUHn2G03Glber3k8cQL2Y= github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb/go.mod h1:GxGqnjWzl1Gz8WfAfMJSfhvsi4EPZayRb25nLHDWXyA= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/errortracker/sentry/sentry.go ================================================ package sentry import ( "context" "fmt" "net" "os" "strings" "time" "github.com/getsentry/sentry-go" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/grpc" "github.com/go-kratos/kratos/v2/transport/http" ) type ctxKey struct{} type Option func(*options) type options struct { repanic bool waitForDelivery bool timeout time.Duration tags map[string]any } // WithRepanic repanic configures whether Sentry should repanic after recovery, in most cases it should be set to true. func WithRepanic(repanic bool) Option { return func(opts *options) { opts.repanic = repanic } } // WithWaitForDelivery waitForDelivery configures whether you want to block the request before moving forward with the response. func WithWaitForDelivery(waitForDelivery bool) Option { return func(opts *options) { opts.waitForDelivery = waitForDelivery } } // WithTimeout timeout for the event delivery requests. func WithTimeout(timeout time.Duration) Option { return func(opts *options) { opts.timeout = timeout } } // WithTags global tags injection, the value type must be string or log.Valuer func WithTags(kvs map[string]any) Option { return func(opts *options) { opts.tags = kvs } } // Server returns a new server middleware for Sentry. func Server(opts ...Option) middleware.Middleware { conf := options{repanic: true} for _, o := range opts { o(&conf) } if conf.timeout == 0 { conf.timeout = 2 * time.Second } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { hub := GetHubFromContext(ctx) scope := hub.Scope() for k, v := range conf.tags { switch val := v.(type) { case string: scope.SetTag(k, val) case log.Valuer: scope.SetTag(k, fmt.Sprintf("%v", val(ctx))) } } if tr, ok := transport.FromServerContext(ctx); ok { switch tr.Kind() { case transport.KindGRPC: gtr := tr.(*grpc.Transport) scope.SetContext("gRPC", map[string]any{ "endpoint": gtr.Endpoint(), "operation": gtr.Operation(), }) headers := make(map[string]any) for _, k := range gtr.RequestHeader().Keys() { headers[k] = gtr.RequestHeader().Get(k) } scope.SetContext("Headers", headers) case transport.KindHTTP: htr := tr.(*http.Transport) r := htr.Request() scope.SetRequest(r) } } ctx = context.WithValue(ctx, ctxKey{}, hub) defer recoverWithSentry(ctx, conf, hub, req) return handler(ctx, req) } } } func recoverWithSentry(ctx context.Context, opts options, hub *sentry.Hub, req any) { if err := recover(); err != nil { if !isBrokenPipeError(err) { eventID := hub.RecoverWithContext( context.WithValue(ctx, sentry.RequestContextKey, req), err, ) if eventID != nil && opts.waitForDelivery { hub.Flush(opts.timeout) } } if opts.repanic { panic(err) } } } func isBrokenPipeError(err any) bool { if netErr, ok := err.(*net.OpError); ok { if sysErr, ok := netErr.Err.(*os.SyscallError); ok { if strings.Contains(strings.ToLower(sysErr.Error()), "broken pipe") || strings.Contains(strings.ToLower(sysErr.Error()), "connection reset by peer") { return true } } } return false } // GetHubFromContext retrieves attached *sentry.Hub instance from context or sentry. // You can use this hub for extra information reporting func GetHubFromContext(ctx context.Context) *sentry.Hub { if hub, ok := ctx.Value(ctxKey{}).(*sentry.Hub); ok { return hub } return sentry.CurrentHub().Clone() } ================================================ FILE: contrib/errortracker/sentry/sentry_test.go ================================================ package sentry import ( "testing" "time" ) func TestWithTags(t *testing.T) { opts := new(options) strval := "bar" kvs := map[string]any{ "foo": strval, } funcTags := WithTags(kvs) funcTags(opts) if opts.tags["foo"].(string) != strval { t.Errorf("TestWithTags() = %v, want %v", opts.tags["foo"].(string), strval) } } func TestWithRepanic(t *testing.T) { opts := new(options) val := true f := WithRepanic(val) f(opts) if opts.repanic != val { t.Errorf("TestWithRepanic() = %v, want %v", opts.repanic, val) } } func TestWithWaitForDelivery(t *testing.T) { opts := new(options) val := true f := WithWaitForDelivery(val) f(opts) if opts.waitForDelivery != val { t.Errorf("TestWithWaitForDelivery() = %v, want %v", opts.waitForDelivery, val) } } func TestWithTimeout(t *testing.T) { opts := new(options) val := time.Second * 10 f := WithTimeout(val) f(opts) if opts.timeout != val { t.Errorf("TestWithTimeout() = %v, want %v", opts.timeout, val) } } ================================================ FILE: contrib/log/aliyun/aliyun.go ================================================ package aliyun import ( "encoding/json" "fmt" "strconv" "time" sls "github.com/aliyun/aliyun-log-go-sdk" "github.com/aliyun/aliyun-log-go-sdk/producer" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/log" ) // Logger see more detail https://github.com/aliyun/aliyun-log-go-sdk type Logger interface { log.Logger GetProducer() *producer.Producer Close() error } type aliyunLog struct { producer *producer.Producer opts *options } func (a *aliyunLog) GetProducer() *producer.Producer { return a.producer } type options struct { accessKey string accessSecret string endpoint string project string logstore string } func defaultOptions() *options { return &options{ project: "projectName", logstore: "app", } } func WithEndpoint(endpoint string) Option { return func(alc *options) { alc.endpoint = endpoint } } func WithProject(project string) Option { return func(alc *options) { alc.project = project } } func WithLogstore(logstore string) Option { return func(alc *options) { alc.logstore = logstore } } func WithAccessKey(ak string) Option { return func(alc *options) { alc.accessKey = ak } } func WithAccessSecret(as string) Option { return func(alc *options) { alc.accessSecret = as } } type Option func(alc *options) func (a *aliyunLog) Close() error { return a.producer.Close(5000) } func (a *aliyunLog) Log(level log.Level, keyvals ...any) error { contents := make([]*sls.LogContent, 0, len(keyvals)/2+1) contents = append(contents, &sls.LogContent{ Key: newString(level.Key()), Value: newString(level.String()), }) for i := 0; i < len(keyvals); i += 2 { contents = append(contents, &sls.LogContent{ Key: newString(toString(keyvals[i])), Value: newString(toString(keyvals[i+1])), }) } logInst := &sls.Log{ Time: proto.Uint32(uint32(time.Now().Unix())), Contents: contents, } return a.producer.SendLog(a.opts.project, a.opts.logstore, "", "", logInst) } // NewAliyunLog new aliyun logger with options. func NewAliyunLog(options ...Option) (Logger, error) { opts := defaultOptions() for _, o := range options { o(opts) } producerConfig := producer.GetDefaultProducerConfig() producerConfig.Endpoint = opts.endpoint producerConfig.AccessKeyID = opts.accessKey producerConfig.AccessKeySecret = opts.accessSecret producerInst, err := producer.NewProducer(producerConfig) if err != nil { return nil, err } producerInst.Start() return &aliyunLog{ opts: opts, producer: producerInst, }, nil } // newString string convert to *string func newString(s string) *string { return &s } // toString convert any type to string func toString(v any) string { var key string if v == nil { return key } switch v := v.(type) { case float64: key = strconv.FormatFloat(v, 'f', -1, 64) case float32: key = strconv.FormatFloat(float64(v), 'f', -1, 32) case int: key = strconv.Itoa(v) case uint: key = strconv.FormatUint(uint64(v), 10) case int8: key = strconv.Itoa(int(v)) case uint8: key = strconv.FormatUint(uint64(v), 10) case int16: key = strconv.Itoa(int(v)) case uint16: key = strconv.FormatUint(uint64(v), 10) case int32: key = strconv.Itoa(int(v)) case uint32: key = strconv.FormatUint(uint64(v), 10) case int64: key = strconv.FormatInt(v, 10) case uint64: key = strconv.FormatUint(v, 10) case string: key = v case bool: key = strconv.FormatBool(v) case []byte: key = string(v) case fmt.Stringer: key = v.String() default: newValue, _ := json.Marshal(v) key = string(newValue) } return key } ================================================ FILE: contrib/log/aliyun/aliyun_test.go ================================================ package aliyun import ( "math" "reflect" "testing" "github.com/go-kratos/kratos/v2/log" ) func TestWithEndpoint(t *testing.T) { opts := new(options) endpoint := "foo" funcEndpoint := WithEndpoint(endpoint) funcEndpoint(opts) if opts.endpoint != endpoint { t.Errorf("WithEndpoint() = %s, want %s", opts.endpoint, endpoint) } } func TestWithProject(t *testing.T) { opts := new(options) project := "foo" funcProject := WithProject(project) funcProject(opts) if opts.project != project { t.Errorf("WithProject() = %s, want %s", opts.project, project) } } func TestWithLogstore(t *testing.T) { opts := new(options) logstore := "foo" funcLogstore := WithLogstore(logstore) funcLogstore(opts) if opts.logstore != logstore { t.Errorf("WithLogatore() = %s, want %s", opts.logstore, logstore) } } func TestWithAccessKey(t *testing.T) { opts := new(options) accessKey := "foo" funcAccessKey := WithAccessKey(accessKey) funcAccessKey(opts) if opts.accessKey != accessKey { t.Errorf("WithAccessKey() = %s, want %s", opts.accessKey, accessKey) } } func TestWithAccessSecret(t *testing.T) { opts := new(options) accessSecret := "foo" funcAccessSecret := WithAccessSecret(accessSecret) funcAccessSecret(opts) if opts.accessSecret != accessSecret { t.Errorf("WithAccessSecret() = %s, want %s", opts.accessSecret, accessSecret) } } func TestLogger(t *testing.T) { project := "foo" logger, err := NewAliyunLog(WithProject(project)) if err != nil { t.Error(err) return } defer logger.Close() logger.GetProducer() flog := log.NewHelper(logger) flog.Debug("log", "test") flog.Info("log", "test") flog.Warn("log", "test") flog.Error("log", "test") } func TestLog(t *testing.T) { project := "foo" logger, err := NewAliyunLog(WithProject(project)) if err != nil { t.Error(err) return } defer logger.Close() err = logger.Log(log.LevelDebug, 0, int8(1), int16(2), int32(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, uint(0), uint8(1), uint16(2), uint32(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, uint(0), uint8(1), uint16(2), uint32(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, int64(0), uint64(1), float32(2), float64(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, []byte{0, 1, 2, 3}, "foo") if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, true, 0) if err != nil { t.Errorf("Log() returns error:%v", err) } } func TestNewString(t *testing.T) { ptr := newString("") if kind := reflect.TypeOf(ptr).Kind(); kind != reflect.Ptr { t.Errorf("want type: %v, got type: %v", reflect.Ptr, kind) } } func TestToString(t *testing.T) { tests := []struct { name string in any out string }{ {"float64", 6.66, "6.66"}, {"max float64", math.MaxFloat64, "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, //nolint:lll {"float32", float32(6.66), "6.66"}, {"max float32", float32(math.MaxFloat32), "340282350000000000000000000000000000000"}, {"int", math.MaxInt64, "9223372036854775807"}, {"uint", uint(math.MaxUint64), "18446744073709551615"}, {"int8", int8(math.MaxInt8), "127"}, {"uint8", uint8(math.MaxUint8), "255"}, {"int16", int16(math.MaxInt16), "32767"}, {"uint16", uint16(math.MaxUint16), "65535"}, {"int32", int32(math.MaxInt32), "2147483647"}, {"uint32", uint32(math.MaxUint32), "4294967295"}, {"int64", int64(math.MaxInt64), "9223372036854775807"}, {"uint64", uint64(math.MaxUint64), "18446744073709551615"}, {"string", "abc", "abc"}, {"bool", false, "false"}, {"[]byte", []byte("abc"), "abc"}, {"struct", struct{ Name string }{}, `{"Name":""}`}, {"nil", nil, ""}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { out := toString(test.in) if test.out != out { t.Fatalf("want: %s, got: %s", test.out, out) } }) } } ================================================ FILE: contrib/log/aliyun/go.mod ================================================ module github.com/go-kratos/kratos/contrib/log/aliyun/v2 go 1.22 require ( github.com/aliyun/aliyun-log-go-sdk v0.1.99 github.com/go-kratos/kratos/v2 v2.9.2 google.golang.org/protobuf v1.33.0 ) require ( github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/go-kit/kit v0.10.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/net v0.33.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/log/aliyun/go.sum ================================================ 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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d h1:wvStE9wLpws31NiWUx+38wny1msZ/tm+eL5xmm4Y7So= github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d/go.mod h1:9XMFaCeRyW7fC9XJOWQ+NdAv8VLG7ys7l3x4ozEGLUQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 h1:7Q2FEyqxeZeIkwYMwRC3uphxV4i7O2eV4ETe21d6lS4= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50= github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/sts-20150401/v2 v2.0.1 h1:CevZp0VdG7Q+1J3qwNj+JL7ztKxsL27+tknbdTK9Y6M= github.com/alibabacloud-go/sts-20150401/v2 v2.0.1/go.mod h1:8wJW1xC4mVcdRXzOvWJYfCCxmvFzZ0VB9iilVjBeWBc= github.com/alibabacloud-go/tea v1.1.19 h1:Xroq0M+pr0mC834Djj3Fl4ZA8+GGoA0i7aWse1vmgf4= github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.1 h1:K6kwgo+UiYx+/kr6CO0PN5ACZDzE3nnn9d77215AkTs= github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M= github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/aliyun-log-go-sdk v0.1.99 h1:gZvHwShUzR6F9ttBLhC4+eEC2oT4NVp5bEroPUxMeDg= github.com/aliyun/aliyun-log-go-sdk v0.1.99/go.mod h1:1NZbf//4a26kGXem8pb3/vc71M+XqRYQgekEZv89y4U= github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 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/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= ================================================ FILE: contrib/log/fluent/fluent.go ================================================ package fluent import ( "fmt" "net" "net/url" "strconv" "time" "github.com/fluent/fluent-logger-golang/fluent" "github.com/go-kratos/kratos/v2/log" ) var _ log.Logger = (*Logger)(nil) // Option is fluentd logger option. type Option func(*options) type options struct { timeout time.Duration writeTimeout time.Duration bufferLimit int retryWait int maxRetry int maxRetryWait int tagPrefix string async bool forceStopAsyncSend bool } // WithTimeout with config Timeout. func WithTimeout(timeout time.Duration) Option { return func(opts *options) { opts.timeout = timeout } } // WithWriteTimeout with config WriteTimeout. func WithWriteTimeout(writeTimeout time.Duration) Option { return func(opts *options) { opts.writeTimeout = writeTimeout } } // WithBufferLimit with config BufferLimit. func WithBufferLimit(bufferLimit int) Option { return func(opts *options) { opts.bufferLimit = bufferLimit } } // WithRetryWait with config RetryWait. func WithRetryWait(retryWait int) Option { return func(opts *options) { opts.retryWait = retryWait } } // WithMaxRetry with config MaxRetry. func WithMaxRetry(maxRetry int) Option { return func(opts *options) { opts.maxRetry = maxRetry } } // WithMaxRetryWait with config MaxRetryWait. func WithMaxRetryWait(maxRetryWait int) Option { return func(opts *options) { opts.maxRetryWait = maxRetryWait } } // WithTagPrefix with config TagPrefix. func WithTagPrefix(tagPrefix string) Option { return func(opts *options) { opts.tagPrefix = tagPrefix } } // WithAsync with config Async. func WithAsync(async bool) Option { return func(opts *options) { opts.async = async } } // WithForceStopAsyncSend with config ForceStopAsyncSend. func WithForceStopAsyncSend(forceStopAsyncSend bool) Option { return func(opts *options) { opts.forceStopAsyncSend = forceStopAsyncSend } } // Logger is fluent logger sdk. type Logger struct { opts options log *fluent.Fluent } // NewLogger new a std logger with options. // target: // // tcp://127.0.0.1:24224 // unix://var/run/fluent/fluent.sock func NewLogger(addr string, opts ...Option) (*Logger, error) { option := options{} for _, o := range opts { o(&option) } u, err := url.Parse(addr) if err != nil { return nil, err } c := fluent.Config{ Timeout: option.timeout, WriteTimeout: option.writeTimeout, BufferLimit: option.bufferLimit, RetryWait: option.retryWait, MaxRetry: option.maxRetry, MaxRetryWait: option.maxRetryWait, TagPrefix: option.tagPrefix, Async: option.async, ForceStopAsyncSend: option.forceStopAsyncSend, } switch u.Scheme { case "tcp": host, port, err2 := net.SplitHostPort(u.Host) if err2 != nil { return nil, err2 } if c.FluentPort, err = strconv.Atoi(port); err != nil { return nil, err } c.FluentNetwork = u.Scheme c.FluentHost = host case "unix": c.FluentNetwork = u.Scheme c.FluentSocketPath = u.Path default: return nil, fmt.Errorf("unknown network: %s", u.Scheme) } fl, err := fluent.New(c) if err != nil { return nil, err } return &Logger{ opts: option, log: fl, }, nil } // Log print the kv pairs log. func (l *Logger) Log(level log.Level, keyvals ...any) error { if len(keyvals) == 0 { return nil } if len(keyvals)%2 != 0 { keyvals = append(keyvals, "KEYVALS UNPAIRED") } data := make(map[string]string, len(keyvals)/2+1) for i := 0; i < len(keyvals); i += 2 { data[fmt.Sprint(keyvals[i])] = fmt.Sprint(keyvals[i+1]) } if err := l.log.Post(level.String(), data); err != nil { println(err) } return nil } // Close close the logger. func (l *Logger) Close() error { return l.log.Close() } ================================================ FILE: contrib/log/fluent/fluent_test.go ================================================ package fluent import ( "io" "net" "os" "testing" "time" "github.com/go-kratos/kratos/v2/log" ) func TestMain(m *testing.M) { listener := func(ln net.Listener) { conn, err := ln.Accept() if err != nil { return } defer conn.Close() _, err = io.ReadAll(conn) if err != nil { return } } if ln, err := net.Listen("tcp", ":24224"); err == nil { defer ln.Close() go func() { for { listener(ln) } }() } os.Exit(m.Run()) } func TestWithTimeout(t *testing.T) { opts := new(options) var duration time.Duration = 1000000000 funcTimeout := WithTimeout(duration) funcTimeout(opts) if opts.timeout != duration { t.Errorf("WithTimeout() = %v, want %v", opts.timeout, duration) } } func TestWithWriteTimeout(t *testing.T) { opts := new(options) var duration time.Duration = 1000000000 funcWriteTimeout := WithWriteTimeout(duration) funcWriteTimeout(opts) if opts.writeTimeout != duration { t.Errorf("WithWriteTimeout() = %v, want %v", opts.writeTimeout, duration) } } func TestWithBufferLimit(t *testing.T) { opts := new(options) bufferLimit := 1000000000 funcBufferLimit := WithBufferLimit(bufferLimit) funcBufferLimit(opts) if opts.bufferLimit != bufferLimit { t.Errorf("WithBufferLimit() = %d, want %d", opts.bufferLimit, bufferLimit) } } func TestWithRetryWait(t *testing.T) { opts := new(options) retryWait := 1000000000 funcRetryWait := WithRetryWait(retryWait) funcRetryWait(opts) if opts.retryWait != retryWait { t.Errorf("WithRetryWait() = %d, want %d", opts.retryWait, retryWait) } } func TestWithMaxRetry(t *testing.T) { opts := new(options) maxRetry := 1000000000 funcMaxRetry := WithMaxRetry(maxRetry) funcMaxRetry(opts) if opts.maxRetry != maxRetry { t.Errorf("WithMaxRetry() = %d, want %d", opts.maxRetry, maxRetry) } } func TestWithMaxRetryWait(t *testing.T) { opts := new(options) maxRetryWait := 1000000000 funcMaxRetryWait := WithMaxRetryWait(maxRetryWait) funcMaxRetryWait(opts) if opts.maxRetryWait != maxRetryWait { t.Errorf("WithMaxRetryWait() = %d, want %d", opts.maxRetryWait, maxRetryWait) } } func TestWithTagPrefix(t *testing.T) { opts := new(options) tagPrefix := "tag_prefix" funcTagPrefix := WithTagPrefix(tagPrefix) funcTagPrefix(opts) if opts.tagPrefix != tagPrefix { t.Errorf("WithTagPrefix() = %s, want %s", opts.tagPrefix, tagPrefix) } } func TestWithAsync(t *testing.T) { opts := new(options) async := true funcAsync := WithAsync(async) funcAsync(opts) if opts.async != async { t.Errorf("WithAsync() = %t, want %t", opts.async, async) } } func TestWithForceStopAsyncSend(t *testing.T) { opts := new(options) forceStopAsyncSend := true funcForceStopAsyncSend := WithForceStopAsyncSend(forceStopAsyncSend) funcForceStopAsyncSend(opts) if opts.forceStopAsyncSend != forceStopAsyncSend { t.Errorf("WithForceStopAsyncSend() = %t, want %t", opts.forceStopAsyncSend, forceStopAsyncSend) } } func TestLogger(t *testing.T) { logger, err := NewLogger("tcp://127.0.0.1:24224") if err != nil { t.Error(err) } defer logger.Close() flog := log.NewHelper(logger) flog.Debug("log", "test") flog.Info("log", "test") flog.Warn("log", "test") flog.Error("log", "test") } func TestLoggerWithOpt(t *testing.T) { var duration time.Duration = 1000000000 logger, err := NewLogger("tcp://127.0.0.1:24224", WithTimeout(duration)) if err != nil { t.Error(err) } defer logger.Close() flog := log.NewHelper(logger) flog.Debug("log", "test") flog.Info("log", "test") flog.Warn("log", "test") flog.Error("log", "test") } func TestLoggerError(t *testing.T) { errCase := []string{ "foo", "tcp://127.0.0.1/", "tcp://127.0.0.1:1234a", "tcp://127.0.0.1:65535", "https://127.0.0.1:8080", "unix://foo/bar", } for _, errc := range errCase { _, err := NewLogger(errc) if err == nil { t.Error(err) } } } func BenchmarkLoggerPrint(b *testing.B) { b.SetParallelism(100) logger, err := NewLogger("tcp://127.0.0.1:24224") flog := log.NewHelper(logger) if err != nil { b.Error(err) } defer logger.Close() b.RunParallel(func(pb *testing.PB) { for pb.Next() { flog.Info("log", "test") } }) } func BenchmarkLoggerHelperV(b *testing.B) { b.SetParallelism(100) logger, err := NewLogger("tcp://127.0.0.1:24224") if err != nil { b.Error(err) } h := log.NewHelper(logger) b.RunParallel(func(pb *testing.PB) { for pb.Next() { h.Info("log", "test") } }) } func BenchmarkLoggerHelperInfo(b *testing.B) { b.SetParallelism(100) logger, err := NewLogger("tcp://127.0.0.1:24224") if err != nil { b.Error(err) } defer logger.Close() h := log.NewHelper(logger) b.RunParallel(func(pb *testing.PB) { for pb.Next() { h.Info("test") } }) } func BenchmarkLoggerHelperInfof(b *testing.B) { b.SetParallelism(100) logger, err := NewLogger("tcp://127.0.0.1:24224") if err != nil { b.Error(err) } defer logger.Close() h := log.NewHelper(logger) b.RunParallel(func(pb *testing.PB) { for pb.Next() { h.Infof("log %s", "test") } }) } func BenchmarkLoggerHelperInfow(b *testing.B) { b.SetParallelism(100) logger, err := NewLogger("tcp://127.0.0.1:24224") if err != nil { b.Error(err) } defer logger.Close() h := log.NewHelper(logger) b.RunParallel(func(pb *testing.PB) { for pb.Next() { h.Infow("log", "test") } }) } ================================================ FILE: contrib/log/fluent/go.mod ================================================ module github.com/go-kratos/kratos/contrib/log/fluent/v2 go 1.22 require ( github.com/fluent/fluent-logger-golang v1.9.0 github.com/go-kratos/kratos/v2 v2.9.2 ) require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/kr/text v0.2.0 // indirect github.com/philhofer/fwd v1.1.1 // indirect github.com/tinylib/msgp v1.1.6 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/log/fluent/go.sum ================================================ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/fluent/fluent-logger-golang v1.9.0 h1:zUdY44CHX2oIUc7VTNZc+4m+ORuO/mldQDA7czhWXEg= github.com/fluent/fluent-logger-golang v1.9.0/go.mod h1:2/HCT/jTy78yGyeNGQLGQsjF3zzzAuy6Xlk6FCMV5eU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw= github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/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.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: contrib/log/logrus/go.mod ================================================ module github.com/go-kratos/kratos/contrib/log/logrus/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/sirupsen/logrus v1.8.1 ) require golang.org/x/sys v0.28.0 // indirect replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/log/logrus/go.sum ================================================ 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ================================================ FILE: contrib/log/logrus/logrus.go ================================================ package logrus import ( "github.com/sirupsen/logrus" "github.com/go-kratos/kratos/v2/log" ) var _ log.Logger = (*Logger)(nil) type Logger struct { log *logrus.Logger } func NewLogger(logger *logrus.Logger) log.Logger { return &Logger{ log: logger, } } func (l *Logger) Log(level log.Level, keyvals ...any) (err error) { var ( logrusLevel logrus.Level fields logrus.Fields = make(map[string]any) msg string ) switch level { case log.LevelDebug: logrusLevel = logrus.DebugLevel case log.LevelInfo: logrusLevel = logrus.InfoLevel case log.LevelWarn: logrusLevel = logrus.WarnLevel case log.LevelError: logrusLevel = logrus.ErrorLevel case log.LevelFatal: logrusLevel = logrus.FatalLevel default: logrusLevel = logrus.DebugLevel } if logrusLevel > l.log.Level { return } if len(keyvals) == 0 { return nil } if len(keyvals)%2 != 0 { keyvals = append(keyvals, "") } for i := 0; i < len(keyvals); i += 2 { key, ok := keyvals[i].(string) if !ok { continue } if key == logrus.FieldKeyMsg { msg, _ = keyvals[i+1].(string) continue } fields[key] = keyvals[i+1] } if len(fields) > 0 { l.log.WithFields(fields).Log(logrusLevel, msg) } else { l.log.Log(logrusLevel, msg) } return } ================================================ FILE: contrib/log/logrus/logrus_test.go ================================================ package logrus import ( "bytes" "strings" "testing" "github.com/sirupsen/logrus" "github.com/go-kratos/kratos/v2/log" ) func TestLoggerLog(t *testing.T) { tests := map[string]struct { level logrus.Level formatter logrus.Formatter logLevel log.Level kvs []any want string }{ "json format": { level: logrus.InfoLevel, formatter: &logrus.JSONFormatter{}, logLevel: log.LevelInfo, kvs: []any{"case", "json format", "msg", "1"}, want: `{"case":"json format","level":"info","msg":"1"`, }, "level unmatch": { level: logrus.InfoLevel, formatter: &logrus.JSONFormatter{}, logLevel: log.LevelDebug, kvs: []any{"case", "level unmatch", "msg", "1"}, want: "", }, "fatal level": { level: logrus.InfoLevel, formatter: &logrus.JSONFormatter{}, logLevel: log.LevelFatal, kvs: []any{"case", "json format", "msg", "1"}, want: `{"case":"json format","level":"fatal","msg":"1"`, }, "no tags": { level: logrus.InfoLevel, formatter: &logrus.JSONFormatter{}, logLevel: log.LevelInfo, kvs: []any{"msg", "1"}, want: `{"level":"info","msg":"1"`, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { output := new(bytes.Buffer) logger := logrus.New() logger.Level = test.level logger.Out = output logger.Formatter = test.formatter wrapped := NewLogger(logger) _ = wrapped.Log(test.logLevel, test.kvs...) if !strings.HasPrefix(output.String(), test.want) { t.Errorf("TestName(%s): %s has not prefix %s", name, output.String(), test.want) } }) } } ================================================ FILE: contrib/log/tencent/go.mod ================================================ module github.com/go-kratos/kratos/contrib/log/tencent/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.2 google.golang.org/protobuf v1.33.0 ) require ( github.com/golang/protobuf v1.5.4 // indirect github.com/klauspost/compress v1.15.1 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect go.uber.org/atomic v1.9.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/log/tencent/go.sum ================================================ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.2 h1:29QoDxUqlFWd0Pgyt4lqGTGMNvJGxVQ3lRx1haRaPnw= github.com/tencentcloud/tencentcloud-cls-sdk-go v1.0.2/go.mod h1:WU+0TXfVbSctEsUUf4KmIKnfr+tknbjcsnx/TrEIPH4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= ================================================ FILE: contrib/log/tencent/tencent.go ================================================ package tencent import ( "encoding/json" "fmt" "strconv" "time" cls "github.com/tencentcloud/tencentcloud-cls-sdk-go" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/log" ) type Logger interface { log.Logger GetProducer() *cls.AsyncProducerClient Close() error } type tencentLog struct { producer *cls.AsyncProducerClient opts *options } func (log *tencentLog) GetProducer() *cls.AsyncProducerClient { return log.producer } type options struct { topicID string accessKey string accessSecret string endpoint string } func defaultOptions() *options { return &options{} } func WithEndpoint(endpoint string) Option { return func(cls *options) { cls.endpoint = endpoint } } func WithTopicID(topicID string) Option { return func(cls *options) { cls.topicID = topicID } } func WithAccessKey(ak string) Option { return func(cls *options) { cls.accessKey = ak } } func WithAccessSecret(as string) Option { return func(cls *options) { cls.accessSecret = as } } type Option func(cls *options) func (log *tencentLog) Close() error { return log.producer.Close(5000) } func (log *tencentLog) Log(level log.Level, keyvals ...any) error { contents := make([]*cls.Log_Content, 0, len(keyvals)/2+1) contents = append(contents, &cls.Log_Content{ Key: newString(level.Key()), Value: newString(level.String()), }) for i := 0; i < len(keyvals); i += 2 { contents = append(contents, &cls.Log_Content{ Key: newString(toString(keyvals[i])), Value: newString(toString(keyvals[i+1])), }) } logInst := &cls.Log{ Time: proto.Int64(time.Now().Unix()), Contents: contents, } return log.producer.SendLog(log.opts.topicID, logInst, nil) } func NewLogger(options ...Option) (Logger, error) { opts := defaultOptions() for _, o := range options { o(opts) } producerConfig := cls.GetDefaultAsyncProducerClientConfig() producerConfig.AccessKeyID = opts.accessKey producerConfig.AccessKeySecret = opts.accessSecret producerConfig.Endpoint = opts.endpoint producerInst, err := cls.NewAsyncProducerClient(producerConfig) if err != nil { return nil, err } producerInst.Start() return &tencentLog{ producer: producerInst, opts: opts, }, nil } func newString(s string) *string { return &s } // toString convert any type to string func toString(v any) string { var key string if v == nil { return key } switch v := v.(type) { case float64: key = strconv.FormatFloat(v, 'f', -1, 64) case float32: key = strconv.FormatFloat(float64(v), 'f', -1, 32) case int: key = strconv.Itoa(v) case uint: key = strconv.FormatUint(uint64(v), 10) case int8: key = strconv.Itoa(int(v)) case uint8: key = strconv.FormatUint(uint64(v), 10) case int16: key = strconv.Itoa(int(v)) case uint16: key = strconv.FormatUint(uint64(v), 10) case int32: key = strconv.Itoa(int(v)) case uint32: key = strconv.FormatUint(uint64(v), 10) case int64: key = strconv.FormatInt(v, 10) case uint64: key = strconv.FormatUint(v, 10) case string: key = v case bool: key = strconv.FormatBool(v) case []byte: key = string(v) case fmt.Stringer: key = v.String() default: newValue, _ := json.Marshal(v) key = string(newValue) } return key } ================================================ FILE: contrib/log/tencent/tencent_test.go ================================================ package tencent import ( "math" "reflect" "testing" "github.com/go-kratos/kratos/v2/log" ) func TestWithEndpoint(t *testing.T) { opts := new(options) endpoint := "eee" funcEndpoint := WithEndpoint(endpoint) funcEndpoint(opts) if opts.endpoint != "eee" { t.Errorf("WithEndpoint() = %s, want %s", opts.endpoint, endpoint) } } func TestWithTopicId(t *testing.T) { opts := new(options) topicID := "ee" funcTopicID := WithTopicID(topicID) funcTopicID(opts) if opts.topicID != "ee" { t.Errorf("WithTopicId() = %s, want %s", opts.endpoint, topicID) } } func TestWithAccessKey(t *testing.T) { opts := new(options) accessKey := "ee" funcAccessKey := WithAccessKey(accessKey) funcAccessKey(opts) if opts.accessKey != "ee" { t.Errorf("WithAccessKey() = %s, want %s", opts.endpoint, accessKey) } } func TestWithAccessSecret(t *testing.T) { opts := new(options) accessSecret := "ee" funcAccessSecret := WithAccessSecret(accessSecret) funcAccessSecret(opts) if opts.accessSecret != "ee" { t.Errorf("WithAccessSecret() = %s, want %s", opts.accessSecret, accessSecret) } } func TestTestLogger(t *testing.T) { topicID := "aa" logger, err := NewLogger( WithTopicID(topicID), WithEndpoint("ap-shanghai.cls.tencentcs.com"), WithAccessKey("a"), WithAccessSecret("b"), ) if err != nil { t.Error(err) return } defer logger.Close() logger.GetProducer() flog := log.NewHelper(logger) flog.Debug("log", "test") flog.Info("log", "test") flog.Warn("log", "test") flog.Error("log", "test") } func TestLog(t *testing.T) { topicID := "foo" logger, err := NewLogger( WithTopicID(topicID), WithEndpoint("ap-shanghai.cls.tencentcs.com"), WithAccessKey("a"), WithAccessSecret("b"), ) if err != nil { t.Error(err) return } defer logger.Close() err = logger.Log(log.LevelDebug, 0, int8(1), int16(2), int32(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, uint(0), uint8(1), uint16(2), uint32(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, uint(0), uint8(1), uint16(2), uint32(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, int64(0), uint64(1), float32(2), float64(3)) if err != nil { t.Errorf("Log() returns error:%v", err) } err = logger.Log(log.LevelDebug, []byte{0, 1, 2, 3}, "foo") if err != nil { t.Errorf("Log() returns error:%v", err) } } func TestNewString(t *testing.T) { ptr := newString("") if kind := reflect.TypeOf(ptr).Kind(); kind != reflect.Ptr { t.Errorf("want type: %v, got type: %v", reflect.Ptr, kind) } } func TestToString(t *testing.T) { tests := []struct { name string in any out string }{ {"float64", 6.66, "6.66"}, {"max float64", math.MaxFloat64, "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, //nolint:lll {"float32", float32(6.66), "6.66"}, {"max float32", float32(math.MaxFloat32), "340282350000000000000000000000000000000"}, {"int", math.MaxInt64, "9223372036854775807"}, {"uint", uint(math.MaxUint64), "18446744073709551615"}, {"int8", int8(math.MaxInt8), "127"}, {"uint8", uint8(math.MaxUint8), "255"}, {"int16", int16(math.MaxInt16), "32767"}, {"uint16", uint16(math.MaxUint16), "65535"}, {"int32", int32(math.MaxInt32), "2147483647"}, {"uint32", uint32(math.MaxUint32), "4294967295"}, {"int64", int64(math.MaxInt64), "9223372036854775807"}, {"uint64", uint64(math.MaxUint64), "18446744073709551615"}, {"string", "abc", "abc"}, {"bool", false, "false"}, {"[]byte", []byte("abc"), "abc"}, {"struct", struct{ Name string }{}, `{"Name":""}`}, {"nil", nil, ""}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { out := toString(test.in) if test.out != out { t.Fatalf("want: %s, got: %s", test.out, out) } }) } } ================================================ FILE: contrib/log/zap/go.mod ================================================ module github.com/go-kratos/kratos/contrib/log/zap/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 go.uber.org/zap v1.26.0 ) require go.uber.org/multierr v1.11.0 // indirect replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/log/zap/go.sum ================================================ 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/log/zap/zap.go ================================================ package zap import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "github.com/go-kratos/kratos/v2/log" ) var _ log.Logger = (*Logger)(nil) type Logger struct { log *zap.Logger msgKey string } type Option func(*Logger) // WithMessageKey with message key. func WithMessageKey(key string) Option { return func(l *Logger) { l.msgKey = key } } func NewLogger(zlog *zap.Logger) *Logger { return &Logger{ log: zlog, msgKey: log.DefaultMessageKey, } } func (l *Logger) Log(level log.Level, keyvals ...any) error { // If logging at this level is completely disabled, skip the overhead of // string formatting. if zapcore.Level(level) < zapcore.DPanicLevel && !l.log.Core().Enabled(zapcore.Level(level)) { return nil } var ( msg = "" keylen = len(keyvals) ) if keylen == 0 || keylen%2 != 0 { l.log.Warn(fmt.Sprint("Keyvalues must appear in pairs: ", keyvals)) return nil } data := make([]zap.Field, 0, (keylen/2)+1) for i := 0; i < keylen; i += 2 { if keyvals[i].(string) == l.msgKey { msg, _ = keyvals[i+1].(string) continue } data = append(data, zap.Any(fmt.Sprint(keyvals[i]), keyvals[i+1])) } switch level { case log.LevelDebug: l.log.Debug(msg, data...) case log.LevelInfo: l.log.Info(msg, data...) case log.LevelWarn: l.log.Warn(msg, data...) case log.LevelError: l.log.Error(msg, data...) case log.LevelFatal: l.log.Fatal(msg, data...) } return nil } func (l *Logger) Sync() error { return l.log.Sync() } func (l *Logger) Close() error { return l.Sync() } ================================================ FILE: contrib/log/zap/zap_test.go ================================================ package zap import ( "testing" "go.uber.org/zap" "go.uber.org/zap/zapcore" "github.com/go-kratos/kratos/v2/log" ) type testWriteSyncer struct { output []string } func (x *testWriteSyncer) Write(p []byte) (n int, err error) { x.output = append(x.output, string(p)) return len(p), nil } func (x *testWriteSyncer) Sync() error { return nil } func TestLogger(t *testing.T) { syncer := &testWriteSyncer{} encoderCfg := zapcore.EncoderConfig{ MessageKey: "msg", LevelKey: "level", NameKey: "logger", EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, } core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), syncer, zap.DebugLevel) zlogger := zap.New(core).WithOptions() logger := NewLogger(zlogger) defer func() { _ = logger.Close() }() zlog := log.NewHelper(logger) zlog.Debugw("log", "debug") zlog.Infow("log", "info") zlog.Warnw("log", "warn") zlog.Errorw("log", "error") zlog.Errorw("log", "error", "except warn") zlog.Info("hello world") except := []string{ "{\"level\":\"debug\",\"msg\":\"\",\"log\":\"debug\"}\n", "{\"level\":\"info\",\"msg\":\"\",\"log\":\"info\"}\n", "{\"level\":\"warn\",\"msg\":\"\",\"log\":\"warn\"}\n", "{\"level\":\"error\",\"msg\":\"\",\"log\":\"error\"}\n", "{\"level\":\"warn\",\"msg\":\"Keyvalues must appear in pairs: [log error except warn]\"}\n", "{\"level\":\"info\",\"msg\":\"hello world\"}\n", // not {"level":"info","msg":"","msg":"hello world"} } for i, s := range except { if s != syncer.output[i] { t.Logf("except=%s, got=%s", s, syncer.output[i]) t.Fail() } } } ================================================ FILE: contrib/log/zerolog/go.mod ================================================ module github.com/go-kratos/kratos/contrib/log/zerolog/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/rs/zerolog v1.30.0 ) require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect golang.org/x/sys v0.28.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/log/zerolog/go.sum ================================================ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= ================================================ FILE: contrib/log/zerolog/zerolog.go ================================================ package zerolog import ( "github.com/rs/zerolog" "github.com/go-kratos/kratos/v2/log" ) var _ log.Logger = (*Logger)(nil) type Logger struct { log *zerolog.Logger } func NewLogger(logger *zerolog.Logger) log.Logger { return &Logger{ log: logger, } } func (l *Logger) Log(level log.Level, keyvals ...any) (err error) { var event *zerolog.Event if len(keyvals) == 0 { return nil } if len(keyvals)%2 != 0 { keyvals = append(keyvals, "") } switch level { case log.LevelDebug: event = l.log.Debug() case log.LevelInfo: event = l.log.Info() case log.LevelWarn: event = l.log.Warn() case log.LevelError: event = l.log.Error() case log.LevelFatal: event = l.log.Fatal() default: event = l.log.Debug() } for i := 0; i < len(keyvals); i += 2 { key, ok := keyvals[i].(string) if !ok { continue } event = event.Any(key, keyvals[i+1]) } event.Send() return } ================================================ FILE: contrib/log/zerolog/zerolog_test.go ================================================ package zerolog import ( "testing" "github.com/rs/zerolog" "github.com/go-kratos/kratos/v2/log" ) type testWriteSyncer struct { output []string } func (x *testWriteSyncer) Write(p []byte) (n int, err error) { x.output = append(x.output, string(p)) return len(p), nil } func TestLogger(t *testing.T) { syncer := &testWriteSyncer{} zerolog.TimeFieldFormat = "2006-01-02 15:04:05.000" zlogger := zerolog.New(syncer) logger := NewLogger(&zlogger) klog := log.NewHelper(logger) klog.Debugw("log", "debug") klog.Infow("log", "info") klog.Warnw("log", "warn") klog.Errorw("log", "error") except := []string{ "{\"level\":\"debug\",\"log\":\"debug\"}\n", "{\"level\":\"info\",\"log\":\"info\"}\n", "{\"level\":\"warn\",\"log\":\"warn\"}\n", "{\"level\":\"error\",\"log\":\"error\"}\n", } for i, s := range except { if s != syncer.output[i] { t.Logf("except=%s, got=%s", s, syncer.output[i]) t.Fail() } } } ================================================ FILE: contrib/middleware/validate/README.md ================================================ # Validator Middleware for Kratos Project This module provides a middleware for Kratos to validate request parameters, using schema defined in `.proto` files. There used to be a middleware named `Validator` in Kratos, which calls the generated validation functions from [PGV](https://github.com/bufbuild/protoc-gen-validate) at runtime. Since PGV has been in [maintenance](https://github.com/bufbuild/protoc-gen-validate/commit/4a8ffc4942463929c4289407cd4b8c8328ff5422), and recommend using [protovalidate](https://github.com/bufbuild/protovalidate) as an alternative. That's why we provide a new middleware that uses the schema definitions and validation functions provided by protovalidate. protovalidate no longer requires code generation at build time, but for compatibility with existing Kratos projects, we enable the legacy mode of protovalidate. For most users, no changes are needed to existing code. **But for users who have manually implemented the Validator interface, you need to migrate the relevant implementation yourself**. ================================================ FILE: contrib/middleware/validate/go.mod ================================================ module github.com/go-kratos/kratos/contrib/middleware/validate/v2 go 1.23.0 toolchain go1.24.6 require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717165733-d22d418d82d8.1 github.com/envoyproxy/protoc-gen-validate v1.2.1 github.com/go-kratos/kratos/v2 v2.9.2 google.golang.org/protobuf v1.36.6 ) require ( buf.build/go/protovalidate v0.14.0 cel.dev/expr v0.23.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/google/cel-go v0.25.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/grpc v1.67.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/middleware/validate/go.sum ================================================ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717165733-d22d418d82d8.1 h1:VahIvw/JagkamVOb0q87Az0zu2tmrzlqvO2IKIGOwnI= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717165733-d22d418d82d8.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= buf.build/go/protovalidate v0.14.0 h1:kr/rC/no+DtRyYX+8KXLDxNnI1rINz0imk5K44ZpZ3A= buf.build/go/protovalidate v0.14.0/go.mod h1:+F/oISho9MO7gJQNYC2VWLzcO1fTPmaTA08SDYJZncA= cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= 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/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY= google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/middleware/validate/internal/testdata/generate.go ================================================ package testdata //go:generate protoc -I . -I ../../../../../third_party --go_out=paths=source_relative:. --validate_out=paths=source_relative,lang=go:. ./test.proto ================================================ FILE: contrib/middleware/validate/internal/testdata/test.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.4 // protoc v5.29.3 // source: test.proto package testdata import ( _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" _ "github.com/envoyproxy/protoc-gen-validate/validate" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) 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 Legacy struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Legacy) Reset() { *x = Legacy{} mi := &file_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Legacy) String() string { return protoimpl.X.MessageStringOf(x) } func (*Legacy) ProtoMessage() {} func (x *Legacy) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Legacy.ProtoReflect.Descriptor instead. func (*Legacy) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } func (x *Legacy) GetName() string { if x != nil { return x.Name } return "" } func (x *Legacy) GetAge() int32 { if x != nil { return x.Age } return 0 } type Mixed struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Mixed) Reset() { *x = Mixed{} mi := &file_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Mixed) String() string { return protoimpl.X.MessageStringOf(x) } func (*Mixed) ProtoMessage() {} func (x *Mixed) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Mixed.ProtoReflect.Descriptor instead. func (*Mixed) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{1} } func (x *Mixed) GetName() string { if x != nil { return x.Name } return "" } func (x *Mixed) GetAge() int32 { if x != nil { return x.Age } return 0 } type Modern struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Modern) Reset() { *x = Modern{} mi := &file_test_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Modern) String() string { return protoimpl.X.MessageStringOf(x) } func (*Modern) ProtoMessage() {} func (x *Modern) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Modern.ProtoReflect.Descriptor instead. func (*Modern) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{2} } func (x *Modern) GetName() string { if x != nil { return x.Name } return "" } func (x *Modern) GetAge() int32 { if x != nil { return x.Age } return 0 } var File_test_proto protoreflect.FileDescriptor var file_test_proto_rawDesc = string([]byte{ 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x40, 0x0a, 0x06, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x05, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x20, 0x12, 0x52, 0x03, 0x61, 0x67, 0x65, 0x22, 0x3f, 0x0a, 0x05, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x05, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x20, 0x12, 0x52, 0x03, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x65, 0x72, 0x6e, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x05, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xba, 0x48, 0x04, 0x1a, 0x02, 0x20, 0x12, 0x52, 0x03, 0x61, 0x67, 0x65, 0x42, 0x4b, 0x5a, 0x49, 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, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2f, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( file_test_proto_rawDescOnce sync.Once file_test_proto_rawDescData []byte ) func file_test_proto_rawDescGZIP() []byte { file_test_proto_rawDescOnce.Do(func() { file_test_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_test_proto_rawDesc), len(file_test_proto_rawDesc))) }) return file_test_proto_rawDescData } var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_test_proto_goTypes = []any{ (*Legacy)(nil), // 0: testdata.Legacy (*Mixed)(nil), // 1: testdata.Mixed (*Modern)(nil), // 2: testdata.Modern } var file_test_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_test_proto_init() } func file_test_proto_init() { if File_test_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_test_proto_rawDesc), len(file_test_proto_rawDesc)), NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_test_proto_goTypes, DependencyIndexes: file_test_proto_depIdxs, MessageInfos: file_test_proto_msgTypes, }.Build() File_test_proto = out.File file_test_proto_goTypes = nil file_test_proto_depIdxs = nil } ================================================ FILE: contrib/middleware/validate/internal/testdata/test.pb.validate.go ================================================ // Code generated by protoc-gen-validate. DO NOT EDIT. // source: test.proto package testdata import ( "bytes" "errors" "fmt" "net" "net/mail" "net/url" "regexp" "sort" "strings" "time" "unicode/utf8" "google.golang.org/protobuf/types/known/anypb" ) // ensure the imports are used var ( _ = bytes.MinRead _ = errors.New("") _ = fmt.Print _ = utf8.UTFMax _ = (*regexp.Regexp)(nil) _ = (*strings.Reader)(nil) _ = net.IPv4len _ = time.Duration(0) _ = (*url.URL)(nil) _ = (*mail.Address)(nil) _ = anypb.Any{} _ = sort.Sort ) // Validate checks the field values on Legacy with the rules defined in the // proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. func (m *Legacy) Validate() error { return m.validate(false) } // ValidateAll checks the field values on Legacy with the rules defined in the // proto definition for this message. If any rules are violated, the result is // a list of violation errors wrapped in LegacyMultiError, or nil if none found. func (m *Legacy) ValidateAll() error { return m.validate(true) } func (m *Legacy) validate(all bool) error { if m == nil { return nil } var errors []error if utf8.RuneCountInString(m.GetName()) < 5 { err := LegacyValidationError{ field: "Name", reason: "value length must be at least 5 runes", } if !all { return err } errors = append(errors, err) } if m.GetAge() <= 18 { err := LegacyValidationError{ field: "Age", reason: "value must be greater than 18", } if !all { return err } errors = append(errors, err) } if len(errors) > 0 { return LegacyMultiError(errors) } return nil } // LegacyMultiError is an error wrapping multiple validation errors returned by // Legacy.ValidateAll() if the designated constraints aren't met. type LegacyMultiError []error // Error returns a concatenation of all the error messages it wraps. func (m LegacyMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) } return strings.Join(msgs, "; ") } // AllErrors returns a list of validation violation errors. func (m LegacyMultiError) AllErrors() []error { return m } // LegacyValidationError is the validation error returned by Legacy.Validate if // the designated constraints aren't met. type LegacyValidationError struct { field string reason string cause error key bool } // Field function returns field value. func (e LegacyValidationError) Field() string { return e.field } // Reason function returns reason value. func (e LegacyValidationError) Reason() string { return e.reason } // Cause function returns cause value. func (e LegacyValidationError) Cause() error { return e.cause } // Key function returns key value. func (e LegacyValidationError) Key() bool { return e.key } // ErrorName returns error name. func (e LegacyValidationError) ErrorName() string { return "LegacyValidationError" } // Error satisfies the builtin error interface func (e LegacyValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) } key := "" if e.key { key = "key for " } return fmt.Sprintf( "invalid %sLegacy.%s: %s%s", key, e.field, e.reason, cause) } var _ error = LegacyValidationError{} var _ interface { Field() string Reason() string Key() bool Cause() error ErrorName() string } = LegacyValidationError{} // Validate checks the field values on Mixed with the rules defined in the // proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. func (m *Mixed) Validate() error { return m.validate(false) } // ValidateAll checks the field values on Mixed with the rules defined in the // proto definition for this message. If any rules are violated, the result is // a list of violation errors wrapped in MixedMultiError, or nil if none found. func (m *Mixed) ValidateAll() error { return m.validate(true) } func (m *Mixed) validate(all bool) error { if m == nil { return nil } var errors []error // no validation rules for Name if m.GetAge() <= 18 { err := MixedValidationError{ field: "Age", reason: "value must be greater than 18", } if !all { return err } errors = append(errors, err) } if len(errors) > 0 { return MixedMultiError(errors) } return nil } // MixedMultiError is an error wrapping multiple validation errors returned by // Mixed.ValidateAll() if the designated constraints aren't met. type MixedMultiError []error // Error returns a concatenation of all the error messages it wraps. func (m MixedMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) } return strings.Join(msgs, "; ") } // AllErrors returns a list of validation violation errors. func (m MixedMultiError) AllErrors() []error { return m } // MixedValidationError is the validation error returned by Mixed.Validate if // the designated constraints aren't met. type MixedValidationError struct { field string reason string cause error key bool } // Field function returns field value. func (e MixedValidationError) Field() string { return e.field } // Reason function returns reason value. func (e MixedValidationError) Reason() string { return e.reason } // Cause function returns cause value. func (e MixedValidationError) Cause() error { return e.cause } // Key function returns key value. func (e MixedValidationError) Key() bool { return e.key } // ErrorName returns error name. func (e MixedValidationError) ErrorName() string { return "MixedValidationError" } // Error satisfies the builtin error interface func (e MixedValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) } key := "" if e.key { key = "key for " } return fmt.Sprintf( "invalid %sMixed.%s: %s%s", key, e.field, e.reason, cause) } var _ error = MixedValidationError{} var _ interface { Field() string Reason() string Key() bool Cause() error ErrorName() string } = MixedValidationError{} // Validate checks the field values on Modern with the rules defined in the // proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. func (m *Modern) Validate() error { return m.validate(false) } // ValidateAll checks the field values on Modern with the rules defined in the // proto definition for this message. If any rules are violated, the result is // a list of violation errors wrapped in ModernMultiError, or nil if none found. func (m *Modern) ValidateAll() error { return m.validate(true) } func (m *Modern) validate(all bool) error { if m == nil { return nil } var errors []error // no validation rules for Name // no validation rules for Age if len(errors) > 0 { return ModernMultiError(errors) } return nil } // ModernMultiError is an error wrapping multiple validation errors returned by // Modern.ValidateAll() if the designated constraints aren't met. type ModernMultiError []error // Error returns a concatenation of all the error messages it wraps. func (m ModernMultiError) Error() string { msgs := make([]string, 0, len(m)) for _, err := range m { msgs = append(msgs, err.Error()) } return strings.Join(msgs, "; ") } // AllErrors returns a list of validation violation errors. func (m ModernMultiError) AllErrors() []error { return m } // ModernValidationError is the validation error returned by Modern.Validate if // the designated constraints aren't met. type ModernValidationError struct { field string reason string cause error key bool } // Field function returns field value. func (e ModernValidationError) Field() string { return e.field } // Reason function returns reason value. func (e ModernValidationError) Reason() string { return e.reason } // Cause function returns cause value. func (e ModernValidationError) Cause() error { return e.cause } // Key function returns key value. func (e ModernValidationError) Key() bool { return e.key } // ErrorName returns error name. func (e ModernValidationError) ErrorName() string { return "ModernValidationError" } // Error satisfies the builtin error interface func (e ModernValidationError) Error() string { cause := "" if e.cause != nil { cause = fmt.Sprintf(" | caused by: %v", e.cause) } key := "" if e.key { key = "key for " } return fmt.Sprintf( "invalid %sModern.%s: %s%s", key, e.field, e.reason, cause) } var _ error = ModernValidationError{} var _ interface { Field() string Reason() string Key() bool Cause() error ErrorName() string } = ModernValidationError{} ================================================ FILE: contrib/middleware/validate/internal/testdata/test.proto ================================================ syntax = "proto3"; package testdata; import "buf/validate/validate.proto"; import "validate/validate.proto"; option go_package = "github.com/go-kratos/kratos/contrib/middleware/validate/internal/testdata"; message Legacy { string name = 1 [(validate.rules).string.min_len = 5]; int32 age = 2 [(validate.rules).int32.gt = 18]; } message Mixed { string name = 1 [(buf.validate.field).string.min_len = 5]; int32 age = 2 [(validate.rules).int32.gt = 18]; } message Modern { string name = 1 [(buf.validate.field).string.min_len = 5]; int32 age = 2 [(buf.validate.field).int32.gt = 18]; } ================================================ FILE: contrib/middleware/validate/validate.go ================================================ package validate import ( "context" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" "buf.build/go/protovalidate" "google.golang.org/protobuf/proto" ) type validator interface { Validate() error } // ProtoValidate is a middleware that validates the request message with [protovalidate](https://github.com/bufbuild/protovalidate) func ProtoValidate() middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if msg, ok := req.(proto.Message); ok { if err := protovalidate.Validate(msg); err != nil { return nil, errors.BadRequest("VALIDATOR", err.Error()).WithCause(err) } } // to compatible with the [old validator](https://github.com/envoyproxy/protoc-gen-validate) if v, ok := req.(validator); ok { if err := v.Validate(); err != nil { return nil, errors.BadRequest("VALIDATOR", err.Error()).WithCause(err) } } return handler(ctx, req) } } } ================================================ FILE: contrib/middleware/validate/validate_test.go ================================================ package validate import ( "context" "testing" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/contrib/middleware/validate/v2/internal/testdata" kerrors "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" ) type testcase struct { name string req proto.Message err bool } func TestTable(t *testing.T) { var mock middleware.Handler = func(context.Context, any) (any, error) { return nil, nil } tests := []testcase{ { name: "valid_legacy", req: &testdata.Legacy{Name: "testcase", Age: 19}, err: false, }, { name: "invalid_legacy1", req: &testdata.Legacy{Name: "testcase", Age: 10}, err: true, }, { name: "invalid_legacy2", req: &testdata.Legacy{Name: "test", Age: 100}, err: true, }, { name: "valid_mixed", req: &testdata.Mixed{Name: "testcase", Age: 19}, err: false, }, { name: "invalid_mixed1", req: &testdata.Mixed{Name: "testcase", Age: 10}, err: true, }, { name: "invalid_mixed2", req: &testdata.Mixed{Name: "test", Age: 100}, err: true, }, { name: "valid_modern", req: &testdata.Modern{Name: "testcase", Age: 19}, err: false, }, { name: "invalid_modern1", req: &testdata.Modern{Name: "testcase", Age: 10}, err: true, }, { name: "invalid_modern2", req: &testdata.Modern{Name: "test", Age: 100}, err: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { handle := ProtoValidate()(mock) _, err := handle(context.Background(), test.req) expect := test.err actual := kerrors.IsBadRequest(err) if expect != actual { t.Errorf("case %s expect %v, actual %v, err %v", test.name, expect, actual, err) } }) } } ================================================ FILE: contrib/opensergo/README.md ================================================ # OpenSergo ## Usage ```go osServer, err := opensergo.New(opensergo.WithEndpoint("localhost:9090")) if err != nil { panic("init opensergo error") } s := &server{} grpcSrv := grpc.NewServer( grpc.Address(":9000"), grpc.Middleware( recovery.Recovery(), ), ) helloworld.RegisterGreeterServer(grpcSrv, s) app := kratos.New( kratos.Name(Name), kratos.Server( grpcSrv, ), ) osServer.ReportMetadata(context.Background(), app) if err := app.Run(); err != nil { log.Fatal(err) } ``` ================================================ FILE: contrib/opensergo/go.mod ================================================ module github.com/go-kratos/kratos/contrib/opensergo/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/opensergo/opensergo-go v0.0.0-20220331070310-e5b01fee4d1c google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 google.golang.org/grpc v1.61.1 google.golang.org/protobuf v1.33.0 ) require ( github.com/go-playground/form/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.4.0 // indirect github.com/kr/text v0.2.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../ ================================================ FILE: contrib/opensergo/go.sum ================================================ 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-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 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/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= 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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/opensergo/opensergo-go v0.0.0-20220331070310-e5b01fee4d1c h1:Ivg+PYq7sOf2IwBGbo9Tt6jk+LHSUapWVPBEr9ygh8Q= github.com/opensergo/opensergo-go v0.0.0-20220331070310-e5b01fee4d1c/go.mod h1:VxL391S2BWXU1m14087xt8+2YTgsnfa+xsSrbuoFKl4= 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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/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-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= 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.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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= ================================================ FILE: contrib/opensergo/opensergo.go ================================================ package opensergo import ( "context" "encoding/json" "net" "net/http" "net/url" "os" "strconv" "time" v1 "github.com/opensergo/opensergo-go/proto/service_contract/v1" "google.golang.org/genproto/googleapis/api/annotations" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "github.com/go-kratos/kratos/v2" ) type Option func(*options) func WithEndpoint(endpoint string) Option { return func(o *options) { o.Endpoint = endpoint } } type options struct { Endpoint string `json:"endpoint"` } func (o *options) ParseJSON(data []byte) error { return json.Unmarshal(data, o) } type OpenSergo struct { mdClient v1.MetadataServiceClient } func New(opts ...Option) (*OpenSergo, error) { opt := options{ Endpoint: os.Getenv("OPENSERGO_ENDPOINT"), } // https://github.com/opensergo/opensergo-specification/blob/main/specification/en/README.md if v := os.Getenv("OPENSERGO_BOOTSTRAP"); v != "" { if err := opt.ParseJSON([]byte(v)); err != nil { return nil, err } } if v := os.Getenv("OPENSERGO_BOOTSTRAP_CONFIG"); v != "" { b, err := os.ReadFile(v) if err != nil { return nil, err } if err := opt.ParseJSON(b); err != nil { return nil, err } } for _, o := range opts { o(&opt) } dialCtx := context.Background() dialCtx, cancel := context.WithTimeout(dialCtx, time.Second) defer cancel() conn, err := grpc.DialContext(dialCtx, opt.Endpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, err } return &OpenSergo{ mdClient: v1.NewMetadataServiceClient(conn), }, nil } func (s *OpenSergo) ReportMetadata(ctx context.Context, app kratos.AppInfo) error { services, types, err := listDescriptors() if err != nil { return err } serviceMetadata := &v1.ServiceMetadata{ ServiceContract: &v1.ServiceContract{ Services: services, Types: types, }, } for _, endpoint := range app.Endpoint() { u, err := url.Parse(endpoint) // nolint if err != nil { return err } host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } portUint64, err := strconv.ParseUint(port, 10, 32) if err != nil { return err } serviceMetadata.Protocols = append(serviceMetadata.Protocols, u.Scheme) serviceMetadata.ListeningAddresses = append(serviceMetadata.ListeningAddresses, &v1.SocketAddress{ Address: host, PortValue: uint32(portUint64), }) } _, err = s.mdClient.ReportMetadata(ctx, &v1.ReportMetadataRequest{ AppName: app.Name(), ServiceMetadata: []*v1.ServiceMetadata{serviceMetadata}, // TODO: Node: *v1.Node, }) return err } func listDescriptors() (services []*v1.ServiceDescriptor, types []*v1.TypeDescriptor, err error) { protoregistry.GlobalFiles.RangeFiles(func(fd protoreflect.FileDescriptor) bool { for i := 0; i < fd.Services().Len(); i++ { var ( methods []*v1.MethodDescriptor sd = fd.Services().Get(i) ) for j := 0; j < sd.Methods().Len(); j++ { md := sd.Methods().Get(j) mName := string(md.Name()) inputType := string(md.Input().FullName()) outputType := string(md.Output().FullName()) isClientStreaming := md.IsStreamingClient() isServerStreaming := md.IsStreamingServer() pattern := proto.GetExtension(md.Options(), annotations.E_Http).(*annotations.HttpRule).GetPattern() var httpPath, httpMethod string if pattern != nil { httpMethod, httpPath = HTTPPatternInfo(pattern) } methodDesc := v1.MethodDescriptor{ Name: mName, InputTypes: []string{inputType}, OutputTypes: []string{outputType}, ClientStreaming: &isClientStreaming, ServerStreaming: &isServerStreaming, HttpPaths: []string{httpPath}, HttpMethods: []string{httpMethod}, // TODO: Description: *string, } methods = append(methods, &methodDesc) } services = append(services, &v1.ServiceDescriptor{ Name: string(sd.Name()), Methods: methods, // TODO: Description: *string, }) } for i := 0; i < fd.Messages().Len(); i++ { var ( fields []*v1.FieldDescriptor md = fd.Messages().Get(i) ) for j := 0; j < md.Fields().Len(); j++ { fd := md.Fields().Get(j) kind := fd.Kind() typeName := kind.String() fields = append(fields, &v1.FieldDescriptor{ Name: string(fd.Name()), Number: int32(fd.Number()), Type: v1.FieldDescriptor_Type(kind), TypeName: &typeName, // TODO: Description: *string, }) } types = append(types, &v1.TypeDescriptor{ Name: string(md.Name()), Fields: fields, }) } return true }) return } func HTTPPatternInfo(pattern any) (method string, path string) { switch p := pattern.(type) { case *annotations.HttpRule_Get: return http.MethodGet, p.Get case *annotations.HttpRule_Post: return http.MethodPost, p.Post case *annotations.HttpRule_Delete: return http.MethodDelete, p.Delete case *annotations.HttpRule_Patch: return http.MethodPatch, p.Patch case *annotations.HttpRule_Put: return http.MethodPut, p.Put case *annotations.HttpRule_Custom: return p.Custom.Kind, p.Custom.Path default: return "", "" } } ================================================ FILE: contrib/opensergo/opensergo_test.go ================================================ package opensergo import ( "context" "net" "net/http" "os" "path/filepath" "reflect" "testing" srvContractPb "github.com/opensergo/opensergo-go/proto/service_contract/v1" "google.golang.org/genproto/googleapis/api/annotations" "google.golang.org/grpc" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" pref "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/descriptorpb" ) type testMetadataServiceServer struct { srvContractPb.UnimplementedMetadataServiceServer } func (m *testMetadataServiceServer) ReportMetadata(_ context.Context, _ *srvContractPb.ReportMetadataRequest) (*srvContractPb.ReportMetadataReply, error) { return &srvContractPb.ReportMetadataReply{}, nil } type testAppInfo struct { id string name string version string metaData map[string]string endpoint []string } func (t testAppInfo) ID() string { return t.id } func (t testAppInfo) Name() string { return t.name } func (t testAppInfo) Version() string { return t.version } func (t testAppInfo) Metadata() map[string]string { return t.metaData } func (t testAppInfo) Endpoint() []string { return t.endpoint } func TestWithEndpoint(t *testing.T) { o := &options{} v := "127.0.0.1:9090" WithEndpoint(v)(o) if !reflect.DeepEqual(v, o.Endpoint) { t.Fatalf("o.Endpoint:%s is not equal to v:%s", o.Endpoint, v) } } func TestOptionsParseJSON(t *testing.T) { want := &options{ Endpoint: "127.0.0.1:9090", } o := &options{} if err := o.ParseJSON([]byte(`{"endpoint":"127.0.0.1:9090"}`)); err != nil { t.Fatalf("o.ParseJSON(v) error:%s", err) } if !reflect.DeepEqual(o, want) { t.Fatalf("o:%v is not equal to want:%v", o, want) } } func TestListDescriptors(t *testing.T) { testPb := &descriptorpb.FileDescriptorProto{ Syntax: proto.String("proto3"), Name: proto.String("test.proto"), Package: proto.String("test"), MessageType: []*descriptorpb.DescriptorProto{ { Name: proto.String("TestMessage"), Field: []*descriptorpb.FieldDescriptorProto{ { Name: proto.String("id"), JsonName: proto.String("id"), Number: proto.Int32(1), Type: descriptorpb.FieldDescriptorProto_Type(pref.Int32Kind).Enum(), }, { Name: proto.String("name"), JsonName: proto.String("name"), Number: proto.Int32(2), Type: descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(), }, }, }, }, Service: []*descriptorpb.ServiceDescriptorProto{ { Name: proto.String("TestService"), Method: []*descriptorpb.MethodDescriptorProto{ { Name: proto.String("Create"), InputType: proto.String("TestMessage"), OutputType: proto.String("TestMessage"), }, }, }, }, } fd, err := protodesc.NewFile(testPb, nil) if err != nil { t.Fatalf("protodesc.NewFile(pb, nil) error:%s", err) } protoregistry.GlobalFiles = new(protoregistry.Files) err = protoregistry.GlobalFiles.RegisterFile(fd) if err != nil { t.Fatalf("protoregistry.GlobalFiles.RegisterFile(fd) error:%s", err) } want := struct { services []*srvContractPb.ServiceDescriptor types []*srvContractPb.TypeDescriptor }{ services: []*srvContractPb.ServiceDescriptor{ { Name: "TestService", Methods: []*srvContractPb.MethodDescriptor{ { Name: "Create", InputTypes: []string{"test.TestMessage"}, OutputTypes: []string{"test.TestMessage"}, ClientStreaming: proto.Bool(false), ServerStreaming: proto.Bool(false), Description: nil, HttpPaths: []string{""}, HttpMethods: []string{""}, }, }, }, }, types: []*srvContractPb.TypeDescriptor{ { Name: "TestMessage", Fields: []*srvContractPb.FieldDescriptor{ { Name: "id", Number: int32(1), Type: srvContractPb.FieldDescriptor_TYPE_INT32, TypeName: proto.String("int32"), }, { Name: "name", Number: int32(2), Type: srvContractPb.FieldDescriptor_TYPE_STRING, TypeName: proto.String("string"), }, }, }, }, } services, types, err := listDescriptors() if err != nil { t.Fatalf("listDescriptors error:%s", err) } if !reflect.DeepEqual(services, want.services) { t.Fatalf("services:%v is not equal to want.services:%v", services, want.services) } if !reflect.DeepEqual(types, want.types) { t.Fatalf("types:%v is not equal to want.types:%v", types, want.types) } } func TestHTTPPatternInfo(t *testing.T) { type args struct { pattern any } tests := []struct { name string args args wantMethod string wantPath string }{ { name: "get", args: args{ pattern: &annotations.HttpRule_Get{Get: "/foo"}, }, wantMethod: http.MethodGet, wantPath: "/foo", }, { name: "post", args: args{ pattern: &annotations.HttpRule_Post{Post: "/foo"}, }, wantMethod: http.MethodPost, wantPath: "/foo", }, { name: "put", args: args{ pattern: &annotations.HttpRule_Put{Put: "/foo"}, }, wantMethod: http.MethodPut, wantPath: "/foo", }, { name: "delete", args: args{ pattern: &annotations.HttpRule_Delete{Delete: "/foo"}, }, wantMethod: http.MethodDelete, wantPath: "/foo", }, { name: "patch", args: args{ pattern: &annotations.HttpRule_Patch{Patch: "/foo"}, }, wantMethod: http.MethodPatch, wantPath: "/foo", }, { name: "custom", args: args{ pattern: &annotations.HttpRule_Custom{ Custom: &annotations.CustomHttpPattern{ Kind: "CUSTOM", Path: "/foo", }, }, }, wantMethod: "CUSTOM", wantPath: "/foo", }, { name: "other", args: args{ pattern: nil, }, wantMethod: "", wantPath: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotMethod, gotPath := HTTPPatternInfo(tt.args.pattern) if gotMethod != tt.wantMethod { t.Errorf("HTTPPatternInfo() gotMethod = %v, want %v", gotMethod, tt.wantMethod) } if gotPath != tt.wantPath { t.Errorf("HTTPPatternInfo() gotPath = %v, want %v", gotPath, tt.wantPath) } }) } } func TestOpenSergo(t *testing.T) { srv := grpc.NewServer() srvContractPb.RegisterMetadataServiceServer(srv, new(testMetadataServiceServer)) lis, err := net.Listen("tcp", "127.0.0.1:9090") if err != nil { t.Fatalf("net.Listen error:%s", err) } go func() { err := srv.Serve(lis) if err != nil { panic(err) } }() app := &testAppInfo{ name: "testApp", endpoint: []string{"//example.com:9090", "//foo.com:9090"}, } type args struct { opts []Option } tests := []struct { name string args args preFunc func(t *testing.T) deferFunc func(t *testing.T) wantErr bool }{ { name: "test_with_opts", args: args{ opts: []Option{ WithEndpoint("127.0.0.1:9090"), }, }, wantErr: false, }, { name: "test_with_env_endpoint", args: args{ opts: []Option{}, }, preFunc: func(_ *testing.T) { err := os.Setenv("OPENSERGO_ENDPOINT", "127.0.0.1:9090") if err != nil { panic(err) } }, wantErr: false, }, { name: "test_with_env_config_file", args: args{ opts: []Option{}, }, preFunc: func(_ *testing.T) { err := os.Setenv("OPENSERGO_BOOTSTRAP", `{"endpoint": "127.0.0.1:9090"}`) if err != nil { panic(err) } }, wantErr: false, }, { name: "test_with_env_bootstrap", args: args{ opts: []Option{}, }, preFunc: func(t *testing.T) { fileContent := `{"endpoint": "127.0.0.1:9090"}` err := os.WriteFile("test.json", []byte(fileContent), 0o644) if err != nil { t.Fatalf("os.WriteFile error:%s", err) } confPath, err := filepath.Abs("./test.json") if err != nil { t.Fatalf("filepath.Abs error:%s", err) } err = os.Setenv("OPENSERGO_BOOTSTRAP_CONFIG", confPath) if err != nil { panic(err) } }, deferFunc: func(t *testing.T) { path := os.Getenv("OPENSERGO_BOOTSTRAP_CONFIG") if path != "" { err := os.Remove(path) if err != nil { t.Fatalf("os.Remove error:%s", err) } } }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preFunc != nil { tt.preFunc(t) } if tt.deferFunc != nil { defer tt.deferFunc(t) } osServer, err := New(tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } err = osServer.ReportMetadata(context.Background(), app) if (err != nil) != tt.wantErr { t.Errorf("ReportMetadata() error = %v, wantErr %v", err, tt.wantErr) return } }) } } ================================================ FILE: contrib/polaris/config.go ================================================ package polaris import ( "context" "path/filepath" "strings" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/model" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/log" ) // ConfigOption is polaris config option. type ConfigOption func(o *configOptions) type configOptions struct { namespace string files []File configFile []polaris.ConfigFile } // WithConfigFile with polaris config file func WithConfigFile(file ...File) ConfigOption { return func(o *configOptions) { o.files = file } } type File struct { Name string Group string } type source struct { client polaris.ConfigAPI options *configOptions } // Load return the config values func (s *source) Load() ([]*config.KeyValue, error) { kvs := make([]*config.KeyValue, 0, len(s.options.files)) for _, file := range s.options.files { configFile, err := s.client.GetConfigFile(s.options.namespace, file.Group, file.Name) if err != nil { return nil, err } s.options.configFile = append(s.options.configFile, configFile) kvs = append(kvs, &config.KeyValue{ Key: file.Name, Value: []byte(configFile.GetContent()), Format: strings.TrimPrefix(filepath.Ext(file.Name), "."), }) } return kvs, nil } // Watch return the watcher func (s *source) Watch() (config.Watcher, error) { return newConfigWatcher(s.options.configFile), nil } type ConfigWatcher struct { event chan model.ConfigFileChangeEvent cfg []*config.KeyValue } func receive(event chan model.ConfigFileChangeEvent) func(m model.ConfigFileChangeEvent) { return func(m model.ConfigFileChangeEvent) { defer func() { if err := recover(); err != nil { log.Error(err) } }() event <- m } } func newConfigWatcher(configFile []polaris.ConfigFile) *ConfigWatcher { w := &ConfigWatcher{ event: make(chan model.ConfigFileChangeEvent, len(configFile)), } for _, file := range configFile { w.cfg = append(w.cfg, &config.KeyValue{ Key: file.GetFileName(), Value: []byte(file.GetContent()), Format: strings.TrimPrefix(filepath.Ext(file.GetFileName()), "."), }) } for _, file := range configFile { file.AddChangeListener(receive(w.event)) } return w } func (w *ConfigWatcher) Next() ([]*config.KeyValue, error) { if event, ok := <-w.event; ok { m := make(map[string]*config.KeyValue) for _, file := range w.cfg { m[file.Key] = file } m[event.ConfigFileMetadata.GetFileName()] = &config.KeyValue{ Key: event.ConfigFileMetadata.GetFileName(), Value: []byte(event.NewValue), Format: strings.TrimPrefix(filepath.Ext(event.ConfigFileMetadata.GetFileName()), "."), } w.cfg = make([]*config.KeyValue, 0, len(m)) for _, kv := range m { w.cfg = append(w.cfg, kv) } return w.cfg, nil } return nil, context.Canceled } func (w *ConfigWatcher) Stop() error { close(w.event) return nil } ================================================ FILE: contrib/polaris/config_test.go ================================================ package polaris import ( "encoding/json" "fmt" "io" "net/http" "reflect" "strings" "testing" "time" "github.com/go-kratos/kratos/v2/config" "github.com/polarismesh/polaris-go" ) var ( testNamespace = "default" testFileGroup = "test" testOriginContent = `server: port: 8080` testUpdatedContent = `server: port: 8090` testCenterURL = "http://127.0.0.1:8090" ) func makeJSONRequest(uri string, data string, method string, headers map[string]string) ([]byte, error) { client := http.Client{} req, err := http.NewRequest(method, uri, strings.NewReader(data)) if err != nil { return nil, err } req.Header.Add("Content-Type", "application/json") for k, v := range headers { req.Header.Add(k, v) } res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() return io.ReadAll(res.Body) } type commonRes struct { Code int32 `json:"code"` } type LoginRes struct { Code int32 `json:"code"` LoginResponse struct { Token string `json:"token"` } `json:"loginResponse"` } type configClient struct { token string } func newConfigClient() (*configClient, error) { token, err := getToken(testCenterURL) if err != nil { return nil, err } return &configClient{ token: token, }, nil } func getToken(testCenterURL string) (string, error) { data, err := json.Marshal(map[string]string{ "name": "polaris", "password": "polaris", }) if err != nil { return "", err } // login use default user res, err := makeJSONRequest(fmt.Sprintf("%s/core/v1/user/login", testCenterURL), string(data), http.MethodPost, map[string]string{}) if err != nil { return "", nil } var loginRes LoginRes if err = json.Unmarshal(res, &loginRes); err != nil { return "", err } return loginRes.LoginResponse.Token, nil } func (client *configClient) createConfigFile(name string) error { data, err := json.Marshal(map[string]string{ "name": name, "namespace": testNamespace, "group": testFileGroup, "content": testOriginContent, "modifyBy": "polaris", "format": "yaml", }) if err != nil { return err } res, err := makeJSONRequest(fmt.Sprintf("%s/config/v1/configfiles", testCenterURL), string(data), http.MethodPost, map[string]string{ "X-Polaris-Token": client.token, }) if err != nil { return err } var resJSON commonRes err = json.Unmarshal(res, &resJSON) if err != nil { return err } if resJSON.Code != 200000 { return fmt.Errorf("create error, res: %s", string(res)) } return nil } func (client *configClient) updateConfigFile(name string) error { data, err := json.Marshal(map[string]string{ "name": name, "namespace": testNamespace, "group": testFileGroup, "content": testUpdatedContent, "modifyBy": "polaris", "format": "yaml", }) if err != nil { return err } res, err := makeJSONRequest(fmt.Sprintf("%s/config/v1/configfiles", testCenterURL), string(data), http.MethodPut, map[string]string{ "X-Polaris-Token": client.token, }) if err != nil { return err } var resJSON commonRes err = json.Unmarshal(res, &resJSON) if err != nil { return err } if resJSON.Code != 200000 { return fmt.Errorf("update error, res: %s", string(res)) } return nil } func (client *configClient) deleteConfigFile(name string) error { data, err := json.Marshal(map[string]string{}) if err != nil { return err } url := fmt.Sprintf("%s/config/v1/configfiles?namespace=%s&group=%s&name=%s", testCenterURL, testNamespace, testFileGroup, name) res, err := makeJSONRequest(url, string(data), http.MethodDelete, map[string]string{ "X-Polaris-Token": client.token, }) if err != nil { return err } var resJSON commonRes err = json.Unmarshal(res, &resJSON) if err != nil { return err } if resJSON.Code != 200000 { return fmt.Errorf("delete error, res: %s", string(res)) } return nil } func (client *configClient) publishConfigFile(name string) error { data, err := json.Marshal(map[string]string{ "namespace": testNamespace, "group": testFileGroup, "fileName": name, "name": name, }) if err != nil { return err } res, err := makeJSONRequest(fmt.Sprintf("%s/config/v1/configfiles/release", testCenterURL), string(data), http.MethodPost, map[string]string{ "X-Polaris-Token": client.token, }) if err != nil { return err } var resJSON commonRes err = json.Unmarshal(res, &resJSON) if err != nil { return err } if resJSON.Code != 200000 { return fmt.Errorf("publish error, res: %s", string(res)) } return nil } func TestConfig(t *testing.T) { name := "kratos-polaris-test.yaml" client, err := newConfigClient() if err != nil { t.Fatal(err) } _ = client.deleteConfigFile(name) if err = client.createConfigFile(name); err != nil { t.Fatal(err) } time.Sleep(5 * time.Second) if err = client.publishConfigFile(name); err != nil { t.Fatal(err) } time.Sleep(5 * time.Second) // Always remember clear test resource sdk, err := polaris.NewSDKContextByAddress("127.0.0.1:8091") if err != nil { t.Fatal(err) } p := New(sdk) config, err := p.Config(WithConfigFile(File{Name: name, Group: testFileGroup})) if err != nil { t.Fatal(err) } kv, err := config.Load() if err != nil { t.Fatal(err) } for _, value := range kv { t.Logf("key: %s, value: %s", value.Key, value.Value) } if len(kv) != 1 || kv[0].Key != name || string(kv[0].Value) != testOriginContent { t.Fatal("config error") } w, err := config.Watch() if err != nil { t.Fatal(err) } t.Cleanup(func() { err = client.deleteConfigFile(name) if err != nil { t.Fatal(err) } }) if err = client.updateConfigFile(name); err != nil { t.Fatal(err) } if err = client.publishConfigFile(name); err != nil { t.Fatal(err) } if kv, err = w.Next(); err != nil { t.Fatal(err) } for _, value := range kv { t.Log(value.Key, string(value.Value)) } if len(kv) != 1 || kv[0].Key != name || string(kv[0].Value) != testUpdatedContent { t.Fatal("config error") } } func TestExtToFormat(t *testing.T) { name := "kratos-polaris-ext.yaml" client, err := newConfigClient() if err != nil { t.Fatal(err) } _ = client.deleteConfigFile(name) if err = client.createConfigFile(name); err != nil { t.Fatal(err) } if err = client.publishConfigFile(name); err != nil { t.Fatal(err) } // Always remember clear test resource t.Cleanup(func() { if err = client.deleteConfigFile(name); err != nil { t.Fatal(err) } }) sdk, err := polaris.NewSDKContextByAddress("127.0.0.1:8091") if err != nil { t.Fatal(err) } p := New(sdk) cfg, err := p.Config(WithConfigFile(File{Name: name, Group: testFileGroup})) if err != nil { t.Fatal(err) } kv, err := cfg.Load() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(len(kv), 1) { t.Errorf("len(kvs) = %d", len(kv)) } if !reflect.DeepEqual(name, kv[0].Key) { t.Errorf("kvs[0].Key is %s", kv[0].Key) } if !reflect.DeepEqual(testOriginContent, string(kv[0].Value)) { t.Errorf("kvs[0].Value is %s", kv[0].Value) } if !reflect.DeepEqual("yaml", kv[0].Format) { t.Errorf("kvs[0].Format is %s", kv[0].Format) } } func TestGetMultipleConfig(t *testing.T) { client, err := newConfigClient() files := make([]File, 0, 3) for i := 0; i < 3; i++ { name := fmt.Sprintf("kratos-polaris-test-%d.yaml", i) if err != nil { t.Fatal(err) } _ = client.deleteConfigFile(name) if err = client.createConfigFile(name); err != nil { t.Fatal(err) } if err = client.publishConfigFile(name); err != nil { t.Fatal(err) } files = append(files, File{Name: name, Group: testFileGroup}) } sdk, err := polaris.NewSDKContextByAddress("127.0.0.1:8091") if err != nil { t.Fatal(err) } p := New(sdk, WithNamespace("default")) cfg, err := p.Config(WithConfigFile(files...)) if err != nil { t.Fatal(err) } kvs, err := cfg.Load() if err != nil { t.Fatal(err) } for _, kv := range kvs { t.Logf("key: %s, value: %s", kv.Key, kv.Value) } w, err := cfg.Watch() if err != nil { t.Fatal(err) } for _, file := range files { if err = client.publishConfigFile(file.Name); err != nil { t.Fatal(err) } kvs, err := w.Next() if err != nil { t.Fatal(err) } m := make(map[string]*config.KeyValue) for _, kv := range kvs { m[kv.Key] = kv } if !reflect.DeepEqual(file.Name, m[file.Name].Key) { t.Errorf("m[file.Name].Key is %s", m[file.Name].Key) } if !reflect.DeepEqual(testOriginContent, string(m[file.Name].Value)) { t.Errorf("m[file.Name].Value is %s", m[file.Name].Value) } if !reflect.DeepEqual("yaml", m[file.Name].Format) { t.Errorf("m[file.Name].Format is %s", m[file.Name].Format) } } } ================================================ FILE: contrib/polaris/go.mod ================================================ module github.com/go-kratos/kratos/contrib/polaris/v2 go 1.23.0 toolchain go1.24.6 require ( github.com/go-kratos/aegis v0.2.0 github.com/go-kratos/kratos/v2 v2.9.2 github.com/google/uuid v1.4.0 github.com/polarismesh/polaris-go v1.3.0 google.golang.org/protobuf v1.33.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/go-playground/form/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.61.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../ ================================================ FILE: contrib/polaris/go.sum ================================================ 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/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/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 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/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/polarismesh/polaris-go v1.3.0 h1:KZKX//ow4OPPoS5+s7h07ptprg+2AcNVGrN6WakC9QM= github.com/polarismesh/polaris-go v1.3.0/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181114220301-adae6a3d119a/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: contrib/polaris/limiter.go ================================================ package polaris import ( "time" "github.com/go-kratos/aegis/ratelimit" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/model" ) type ( // LimiterOption function for polaris limiter LimiterOption func(*limiterOptions) ) type limiterOptions struct { // required, polaris limit namespace namespace string // required, polaris limit service name service string // optional, polaris limit request timeout // max value is (1+RetryCount) * Timeout timeout time.Duration // optional, polaris limit retryCount // init by polaris config retryCount int // optional, request limit quota token uint32 } // WithLimiterNamespace with limiter namespace. func WithLimiterNamespace(namespace string) LimiterOption { return func(o *limiterOptions) { o.namespace = namespace } } // WithLimiterService with limiter service. func WithLimiterService(service string) LimiterOption { return func(o *limiterOptions) { o.service = service } } // WithLimiterTimeout with limiter arguments. func WithLimiterTimeout(timeout time.Duration) LimiterOption { return func(o *limiterOptions) { o.timeout = timeout } } // WithLimiterRetryCount with limiter retryCount. func WithLimiterRetryCount(retryCount int) LimiterOption { return func(o *limiterOptions) { o.retryCount = retryCount } } // WithLimiterToken with limiter token. func WithLimiterToken(token uint32) LimiterOption { return func(o *limiterOptions) { o.token = token } } type Limiter struct { // polaris limit api limitAPI polaris.LimitAPI opts limiterOptions } // init quotaRequest func buildRequest(opts limiterOptions) polaris.QuotaRequest { quotaRequest := polaris.NewQuotaRequest() quotaRequest.SetNamespace(opts.namespace) quotaRequest.SetRetryCount(opts.retryCount) quotaRequest.SetService(opts.service) quotaRequest.SetTimeout(opts.timeout) quotaRequest.SetToken(opts.token) return quotaRequest } // Allow interface impl func (l *Limiter) Allow(method string, argument ...model.Argument) (ratelimit.DoneFunc, error) { request := buildRequest(l.opts) request.SetMethod(method) for _, arg := range argument { request.AddArgument(arg) } resp, err := l.limitAPI.GetQuota(request) if err != nil { // ignore err return func(ratelimit.DoneInfo) {}, nil } if resp.Get().Code == model.QuotaResultOk { return func(ratelimit.DoneInfo) {}, nil } return nil, ratelimit.ErrLimitExceed } ================================================ FILE: contrib/polaris/polaris.go ================================================ package polaris import ( "errors" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/api" "github.com/go-kratos/kratos/v2/config" ) type Polaris struct { router polaris.RouterAPI config polaris.ConfigAPI limit polaris.LimitAPI registry polaris.ProviderAPI discovery polaris.ConsumerAPI namespace string service string } // Option is polaris option. type Option func(o *Polaris) // WithNamespace with polaris global testNamespace func WithNamespace(ns string) Option { return func(o *Polaris) { o.namespace = ns } } // WithService set the current service name func WithService(service string) Option { return func(o *Polaris) { o.service = service } } // New polaris Service governance. func New(sdk api.SDKContext, opts ...Option) Polaris { op := Polaris{ router: polaris.NewRouterAPIByContext(sdk), config: polaris.NewConfigAPIByContext(sdk), limit: polaris.NewLimitAPIByContext(sdk), registry: polaris.NewProviderAPIByContext(sdk), discovery: polaris.NewConsumerAPIByContext(sdk), namespace: "default", } for _, option := range opts { option(&op) } return op } func (p *Polaris) Config(opts ...ConfigOption) (config.Source, error) { options := &configOptions{ namespace: p.namespace, } for _, opt := range opts { opt(options) } if len(options.files) == 0 { return nil, errors.New("fileNames invalid") } return &source{ client: p.config, options: options, }, nil } func (p *Polaris) Registry(opts ...RegistryOption) (r *Registry) { op := registryOptions{ Namespace: p.namespace, Healthy: true, } for _, option := range opts { option(&op) } return &Registry{ opt: op, provider: p.registry, consumer: p.discovery, } } func (p *Polaris) Limiter(opts ...LimiterOption) (r Limiter) { op := limiterOptions{ namespace: p.namespace, service: p.service, } for _, option := range opts { option(&op) } return Limiter{ limitAPI: p.limit, opts: op, } } ================================================ FILE: contrib/polaris/ratelimit.go ================================================ package polaris import ( "context" "strings" "github.com/go-kratos/aegis/ratelimit" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http" "github.com/polarismesh/polaris-go/pkg/model" ) // ErrLimitExceed is service unavailable due to rate limit exceeded. var ( ErrLimitExceed = errors.New(429, "RATELIMIT", "service unavailable due to rate limit exceeded") ) // Ratelimit Request rate limit middleware func Ratelimit(l Limiter) middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if tr, ok := transport.FromServerContext(ctx); ok { var args []model.Argument headers := tr.RequestHeader() // handle header for _, header := range headers.Keys() { args = append(args, model.BuildHeaderArgument(header, headers.Get(header))) } // handle http if ht, ok := tr.(*http.Transport); ok { // url query for key, values := range ht.Request().URL.Query() { args = append(args, model.BuildQueryArgument(key, strings.Join(values, ","))) } } done, e := l.Allow(tr.Operation(), args...) if e != nil { // rejected return nil, ErrLimitExceed } // allowed reply, err = handler(ctx, req) done(ratelimit.DoneInfo{Err: err}) return } return reply, nil } } } ================================================ FILE: contrib/polaris/registry.go ================================================ package polaris import ( "context" "net" "net/url" "strconv" "time" "github.com/google/uuid" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/model" "github.com/go-kratos/kratos/v2/registry" ) var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) type registryOptions struct { // required, testNamespace in polaris Namespace string // required, service access token ServiceToken string // service weight in polaris. Default value is 100, 0 <= weight <= 10000 Weight int // service priority. Default value is 0. The smaller the value, the lower the priority Priority int // To show service is healthy or not. Default value is True . Healthy bool // To show service is isolate or not. Default value is False . Isolate bool // TTL timeout. if node needs to use heartbeat to report,required. If not set,server will throw ErrorCode-400141 TTL int // optional, Timeout for single query. Default value is global config // Total is (1+RetryCount) * Timeout Timeout time.Duration // optional, retry count. Default value is global config RetryCount int } // RegistryOption is polaris option. type RegistryOption func(o *registryOptions) // Registry is polaris registry. type Registry struct { opt registryOptions provider polaris.ProviderAPI consumer polaris.ConsumerAPI } // WithRegistryServiceToken with ServiceToken option. func WithRegistryServiceToken(serviceToken string) RegistryOption { return func(o *registryOptions) { o.ServiceToken = serviceToken } } // WithRegistryWeight with Weight option. func WithRegistryWeight(weight int) RegistryOption { return func(o *registryOptions) { o.Weight = weight } } // WithRegistryHealthy with Healthy option. func WithRegistryHealthy(healthy bool) RegistryOption { return func(o *registryOptions) { o.Healthy = healthy } } // WithRegistryIsolate with Isolate option. func WithRegistryIsolate(isolate bool) RegistryOption { return func(o *registryOptions) { o.Isolate = isolate } } // WithRegistryTTL with TTL option. func WithRegistryTTL(TTL int) RegistryOption { return func(o *registryOptions) { o.TTL = TTL } } // WithRegistryTimeout with Timeout option. func WithRegistryTimeout(timeout time.Duration) RegistryOption { return func(o *registryOptions) { o.Timeout = timeout } } // WithRegistryRetryCount with RetryCount option. func WithRegistryRetryCount(retryCount int) RegistryOption { return func(o *registryOptions) { o.RetryCount = retryCount } } // Register the registration. func (r *Registry) Register(_ context.Context, instance *registry.ServiceInstance) error { id := uuid.NewString() for _, endpoint := range instance.Endpoints { u, err := url.Parse(endpoint) if err != nil { return err } host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } portNum, err := strconv.Atoi(port) if err != nil { return err } // metadata rmd := mapClone(instance.Metadata) if rmd == nil { rmd = make(map[string]string) } rmd["merge"] = id if _, ok := rmd["weight"]; !ok { rmd["weight"] = strconv.Itoa(r.opt.Weight) } weight, _ := strconv.Atoi(rmd["weight"]) _, err = r.provider.RegisterInstance( &polaris.InstanceRegisterRequest{ InstanceRegisterRequest: model.InstanceRegisterRequest{ Service: instance.Name, ServiceToken: r.opt.ServiceToken, Namespace: r.opt.Namespace, Host: host, Port: portNum, Protocol: &u.Scheme, Weight: &weight, Priority: &r.opt.Priority, Version: &instance.Version, Metadata: rmd, Healthy: &r.opt.Healthy, Isolate: &r.opt.Isolate, TTL: &r.opt.TTL, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, }, }, ) if err != nil { return err } } return nil } // Deregister the registration. func (r *Registry) Deregister(_ context.Context, serviceInstance *registry.ServiceInstance) error { for _, endpoint := range serviceInstance.Endpoints { // get url u, err := url.Parse(endpoint) if err != nil { return err } // get host and port host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } // port to int portNum, err := strconv.Atoi(port) if err != nil { return err } // Deregister err = r.provider.Deregister( &polaris.InstanceDeRegisterRequest{ InstanceDeRegisterRequest: model.InstanceDeRegisterRequest{ Service: serviceInstance.Name, ServiceToken: r.opt.ServiceToken, Namespace: r.opt.Namespace, Host: host, Port: portNum, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, }, }, ) if err != nil { return err } } return nil } // GetService return the service instances in memory according to the service name. func (r *Registry) GetService(_ context.Context, serviceName string) ([]*registry.ServiceInstance, error) { // get all instances instancesResponse, err := r.consumer.GetInstances(&polaris.GetInstancesRequest{ GetInstancesRequest: model.GetInstancesRequest{ Service: serviceName, Namespace: r.opt.Namespace, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, SkipRouteFilter: true, }, }) if err != nil { return nil, err } serviceInstances := instancesToServiceInstances(merge(instancesResponse.GetInstances())) return serviceInstances, nil } func merge(instances []model.Instance) map[string][]model.Instance { m := make(map[string][]model.Instance) for _, instance := range instances { if v, ok := m[instance.GetMetadata()["merge"]]; ok { m[instance.GetMetadata()["merge"]] = append(v, instance) } else { m[instance.GetMetadata()["merge"]] = []model.Instance{instance} } } return m } // Watch creates a watcher according to the service name. func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { return newWatcher(ctx, r.opt.Namespace, serviceName, r.consumer) } type Watcher struct { ServiceName string Namespace string Ctx context.Context Cancel context.CancelFunc Channel <-chan model.SubScribeEvent service *model.InstancesResponse ServiceInstances map[string][]model.Instance first bool } func newWatcher(ctx context.Context, namespace string, serviceName string, consumer polaris.ConsumerAPI) (*Watcher, error) { watchServiceResponse, err := consumer.WatchService(&polaris.WatchServiceRequest{ WatchServiceRequest: model.WatchServiceRequest{ Key: model.ServiceKey{ Namespace: namespace, Service: serviceName, }, }, }) if err != nil { return nil, err } w := &Watcher{ Namespace: namespace, ServiceName: serviceName, Channel: watchServiceResponse.EventChannel, service: watchServiceResponse.GetAllInstancesResp, ServiceInstances: merge(watchServiceResponse.GetAllInstancesResp.GetInstances()), } w.Ctx, w.Cancel = context.WithCancel(ctx) return w, nil } // Next returns services in the following two cases: // 1.the first time to watch and the service instance list is not empty. // 2.any service instance changes found. // if the above two conditions are not met, it will block until context deadline exceeded or canceled func (w *Watcher) Next() ([]*registry.ServiceInstance, error) { if !w.first { w.first = true if len(w.ServiceInstances) > 0 { return instancesToServiceInstances(w.ServiceInstances), nil } } select { case <-w.Ctx.Done(): return nil, w.Ctx.Err() case event := <-w.Channel: if event.GetSubScribeEventType() == model.EventInstance { // this always true, but we need to check it to make sure EventType not change if instanceEvent, ok := event.(*model.InstanceEvent); ok { // handle DeleteEvent if instanceEvent.DeleteEvent != nil { for _, instance := range instanceEvent.DeleteEvent.Instances { delete(w.ServiceInstances, instance.GetMetadata()["merge"]) } } // handle UpdateEvent if instanceEvent.UpdateEvent != nil { for _, update := range instanceEvent.UpdateEvent.UpdateList { if v, ok := w.ServiceInstances[update.After.GetMetadata()["merge"]]; ok { var nv []model.Instance m := map[string]model.Instance{} for _, ins := range v { m[ins.GetId()] = ins } m[update.After.GetId()] = update.After for _, ins := range m { if ins.IsHealthy() { nv = append(nv, ins) } } w.ServiceInstances[update.After.GetMetadata()["merge"]] = nv if len(nv) == 0 { delete(w.ServiceInstances, update.After.GetMetadata()["merge"]) } } else { if update.After.IsHealthy() { w.ServiceInstances[update.After.GetMetadata()["merge"]] = []model.Instance{update.After} } } } } // handle AddEvent if instanceEvent.AddEvent != nil { for _, instance := range instanceEvent.AddEvent.Instances { if v, ok := w.ServiceInstances[instance.GetMetadata()["merge"]]; ok { var nv []model.Instance m := map[string]model.Instance{} for _, ins := range v { m[ins.GetId()] = ins } m[instance.GetId()] = instance for _, ins := range m { if ins.IsHealthy() { nv = append(nv, ins) } } if len(nv) != 0 { w.ServiceInstances[instance.GetMetadata()["merge"]] = nv } } else { if instance.IsHealthy() { w.ServiceInstances[instance.GetMetadata()["merge"]] = []model.Instance{instance} } } } } } return instancesToServiceInstances(w.ServiceInstances), nil } } return instancesToServiceInstances(w.ServiceInstances), nil } // Stop close the watcher. func (w *Watcher) Stop() error { w.Cancel() return nil } func instancesToServiceInstances(instances map[string][]model.Instance) []*registry.ServiceInstance { serviceInstances := make([]*registry.ServiceInstance, 0, len(instances)) for _, inss := range instances { if len(inss) == 0 { continue } ins := ®istry.ServiceInstance{ ID: inss[0].GetId(), Name: inss[0].GetService(), Version: inss[0].GetVersion(), Metadata: inss[0].GetMetadata(), } for _, item := range inss { if item.IsHealthy() { ins.Endpoints = append(ins.Endpoints, item.GetProtocol()+"://"+net.JoinHostPort(item.GetHost(), strconv.FormatUint(uint64(item.GetPort()), 10))) } } if len(ins.Endpoints) != 0 { serviceInstances = append(serviceInstances, ins) } } return serviceInstances } // Clone returns a copy of m. This is a shallow clone: // the new keys and values are set using ordinary assignment. func mapClone[M ~map[K]V, K comparable, V any](m M) M { // Preserve nil in case it matters. if m == nil { return nil } // Make a shallow copy of the map. m2 := make(M, len(m)) for k, v := range m { m2[k] = v } return m2 } ================================================ FILE: contrib/polaris/registry_test.go ================================================ package polaris import ( "context" "strconv" "testing" "time" "github.com/polarismesh/polaris-go" "github.com/go-kratos/kratos/v2/registry" ) // TestRegistry func TestRegistry(t *testing.T) { sdk, err := polaris.NewSDKContextByAddress("127.0.0.1:8091") if err != nil { t.Fatal(err) } p := New(sdk) r := p.Registry( WithRegistryTimeout(time.Second), WithRegistryHealthy(true), WithRegistryIsolate(false), WithRegistryRetryCount(3), WithRegistryWeight(100), WithRegistryTTL(1000), ) mm := map[string]string{ "test1": "test1", } ins := ®istry.ServiceInstance{ ID: "test-ut", Name: "test-ut", Version: "v1.0.0", Endpoints: []string{ "grpc://127.0.0.1:8080", "http://127.0.0.1:9090", }, Metadata: mm, } go func() { for i := 0; true; i++ { str := "test" + strconv.Itoa(i) _ = mm[str] if i > 100 { i = 0 } } }() err = r.Register(context.Background(), ins) t.Cleanup(func() { if err = r.Deregister(context.Background(), ins); err != nil { t.Fatal(err) } }) if err != nil { t.Fatal(err) } time.Sleep(time.Second * 3) service, err := r.GetService(context.Background(), "test-ut") if err != nil { t.Fatal(err) } t.Log(service) } ================================================ FILE: contrib/polaris/router.go ================================================ package polaris import ( "context" "net" "strconv" "strings" "github.com/polarismesh/polaris-go" "github.com/polarismesh/polaris-go/pkg/model" "github.com/polarismesh/polaris-go/pkg/model/local" "github.com/polarismesh/polaris-go/pkg/model/pb" v1 "github.com/polarismesh/polaris-go/pkg/model/pb/v1" "google.golang.org/protobuf/types/known/wrapperspb" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http" ) type router struct { service string } type RouterOption func(o *router) // WithRouterService set the caller service name used by the route func WithRouterService(service string) RouterOption { return func(o *router) { o.service = service } } // NodeFilter polaris dynamic router selector func (p *Polaris) NodeFilter(opts ...RouterOption) selector.NodeFilter { o := router{service: p.service} for _, opt := range opts { opt(&o) } return func(ctx context.Context, nodes []selector.Node) []selector.Node { if len(nodes) == 0 { return nodes } req := &polaris.ProcessRoutersRequest{ ProcessRoutersRequest: model.ProcessRoutersRequest{ SourceService: model.ServiceInfo{Namespace: p.namespace, Service: o.service}, DstInstances: buildPolarisInstance(p.namespace, nodes), }, } if appInfo, ok := kratos.FromContext(ctx); ok { req.SourceService.Service = appInfo.Name() } req.AddArguments(model.BuildCallerServiceArgument(p.namespace, req.SourceService.Service)) // process transport if tr, ok := transport.FromClientContext(ctx); ok { req.AddArguments(model.BuildMethodArgument(tr.Operation())) req.AddArguments(model.BuildPathArgument(tr.Operation())) for _, key := range tr.RequestHeader().Keys() { req.AddArguments(model.BuildHeaderArgument(strings.ToLower(key), tr.RequestHeader().Get(key))) } // http if ht, ok := tr.(http.Transporter); ok { req.AddArguments(model.BuildPathArgument(ht.Request().URL.Path)) req.AddArguments(model.BuildCallerIPArgument(ht.Request().RemoteAddr)) // cookie for _, cookie := range ht.Request().Cookies() { req.AddArguments(model.BuildCookieArgument(cookie.Name, cookie.Value)) } // url query for key, values := range ht.Request().URL.Query() { req.AddArguments(model.BuildQueryArgument(key, strings.Join(values, ","))) } } } n := make(map[string]selector.Node, len(nodes)) for _, node := range nodes { n[node.Address()] = node } m, err := p.router.ProcessRouters(req) if err != nil { log.Errorf("polaris process routers failed, err=%v", err) return nodes } newNode := make([]selector.Node, 0, len(m.Instances)) for _, ins := range m.GetInstances() { if v, ok := n[net.JoinHostPort(ins.GetHost(), strconv.FormatUint(uint64(ins.GetPort()), 10))]; ok { newNode = append(newNode, v) } } if len(newNode) == 0 { return nodes } return newNode } } func buildPolarisInstance(namespace string, nodes []selector.Node) *pb.ServiceInstancesInProto { ins := make([]*v1.Instance, 0, len(nodes)) for _, node := range nodes { host, port, err := net.SplitHostPort(node.Address()) if err != nil { log.Errorf("split host port failed error: %v", err) return nil } portUint64, err := strconv.ParseUint(port, 10, 32) if err != nil { log.Errorf("parse port failed error: %v", err) return nil } ins = append(ins, &v1.Instance{ Id: wrapperspb.String(node.Metadata()["merge"]), Service: wrapperspb.String(node.ServiceName()), Namespace: wrapperspb.String(namespace), Host: wrapperspb.String(host), Port: wrapperspb.UInt32(uint32(portUint64)), Protocol: wrapperspb.String(node.Scheme()), Version: wrapperspb.String(node.Version()), Weight: wrapperspb.UInt32(uint32(*node.InitialWeight())), Metadata: node.Metadata(), }) } d := &v1.DiscoverResponse{ Code: wrapperspb.UInt32(1), Info: wrapperspb.String("ok"), Type: v1.DiscoverResponse_INSTANCE, Service: &v1.Service{Name: wrapperspb.String(nodes[0].ServiceName()), Namespace: wrapperspb.String("default")}, Instances: ins, } return pb.NewServiceInstancesInProto(d, func(string) local.InstanceLocalValue { return local.NewInstanceLocalValue() }, &pb.SvcPluginValues{Routers: nil, Loadbalancer: nil}, nil) } ================================================ FILE: contrib/polaris/router_test.go ================================================ package polaris import ( "context" "encoding/json" "fmt" "net/http" "testing" "time" "github.com/polarismesh/polaris-go" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) func TestRouter(t *testing.T) { token, err := getToken("http://127.0.0.1:8090") if err != nil { t.Fatal(err) } data := ` [ { "name":"kratos", "enable":false, "description":"123", "priority":2, "routing_config":{ "@type":"type.googleapis.com/v2.RuleRoutingConfig", "sources":[ { "service":"*", "namespace":"*", "arguments":[ ] } ], "destinations":[ { "labels":{ "az":{ "value":"1", "value_type":"TEXT", "type":"EXACT" } }, "weight":100, "priority":1, "isolate":false, "name":"实例分组1", "namespace":"default", "service":"test-ut" } ] } } ] ` res, err := makeJSONRequest("http://127.0.0.1:8090/naming/v2/routings", data, http.MethodPost, map[string]string{ "X-Polaris-Token": token, }) if err != nil { t.Fatal(err) } resJSON := struct { Code int `json:"code"` Responses []struct { Data struct { ID string `json:"id"` } } `json:"responses"` }{} err = json.Unmarshal(res, &resJSON) if err != nil { t.Fatal(err, string(res)) } if resJSON.Code != 200000 { t.Fatal("create failed", string(res)) } // enable router enableData := fmt.Sprintf(`[{"id":"%s","enable":true}]`, resJSON.Responses[0].Data.ID) res, err = makeJSONRequest("http://127.0.0.1:8090/naming/v2/routings/enable", enableData, http.MethodPut, map[string]string{ "X-Polaris-Token": token, }) if err != nil { t.Fatal(err) } err = json.Unmarshal(res, &resJSON) if err != nil { t.Fatal(err) } if resJSON.Code != 200000 { t.Fatal("enable failed", string(res)) } t.Cleanup(func() { enableData := fmt.Sprintf(`[{"id":"%s"}]`, resJSON.Responses[0].Data.ID) res, err = makeJSONRequest("http://127.0.0.1:8090/naming/v2/routings/delete", enableData, http.MethodPost, map[string]string{ "X-Polaris-Token": token, }) resJSON := &commonRes{} err = json.Unmarshal(res, resJSON) if err != nil { t.Fatal(err, string(res)) } if resJSON.Code != 200000 { t.Fatal("delete failed", string(res)) } }) sdk, err := polaris.NewSDKContextByAddress("127.0.0.1:8091") if err != nil { t.Fatal(err) } p := New(sdk) r := p.Registry( WithRegistryTimeout(time.Second), WithRegistryHealthy(true), WithRegistryIsolate(false), WithRegistryRetryCount(0), WithRegistryWeight(100), WithRegistryTTL(10), ) ins := ®istry.ServiceInstance{ ID: "kratos", Name: "kratos", Version: "v1.0.0", Endpoints: []string{ "grpc://127.0.0.1:8080", "http://127.0.0.1:9090", }, } err = r.Register(context.Background(), ins) if err != nil { t.Fatal(err) } time.Sleep(time.Second * 5) nodes := []selector.Node{ selector.NewNode("grpc", "127.0.0.1:9000", ®istry.ServiceInstance{ ID: "123", Name: "test-ut", Version: "v1.0.0", Metadata: map[string]string{"weight": "100", "az": "1"}, Endpoints: []string{"grpc://127.0.0.1:9000"}, }), selector.NewNode("grpc", "127.0.0.2:9000", ®istry.ServiceInstance{ ID: "123", Name: "test-ut", Version: "v1.0.0", Metadata: map[string]string{"weight": "100", "az": "2"}, Endpoints: []string{"grpc://127.0.0.2:9000"}, }), selector.NewNode("grpc", "127.0.0.3:9000", ®istry.ServiceInstance{ ID: "123", Name: "test-ut", Version: "v1.0.0", Metadata: map[string]string{"weight": "100", "az": "1"}, Endpoints: []string{"grpc://127.0.0.3:9000"}, }), } f := p.NodeFilter() ctx := kratos.NewContext(context.Background(), &mockApp{}) n := f(ctx, nodes) for _, node := range n { if node.Metadata()["az"] != "1" { t.Fatal("node filter result wrong") } t.Log(node) } if len(n) != 2 { t.Fatal("node filter result wrong") } } type mockApp struct{} func (m mockApp) ID() string { return "1" } func (m mockApp) Name() string { return "kratos" } func (m mockApp) Version() string { return "v2.0.0" } func (m mockApp) Metadata() map[string]string { return map[string]string{} } func (m mockApp) Endpoint() []string { return []string{"grpc://123.123.123.123:9090"} } ================================================ FILE: contrib/registry/consul/client.go ================================================ package consul import ( "context" "errors" "fmt" "math/rand/v2" "net" "net/url" "strconv" "strings" "sync" "time" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" "github.com/hashicorp/consul/api" ) type Datacenter string const ( SingleDatacenter Datacenter = "SINGLE" MultiDatacenter Datacenter = "MULTI" ) // Client is consul client config type Client struct { dc Datacenter cli *api.Client // resolve service entry endpoints resolver ServiceResolver // healthcheck time interval in seconds healthcheckInterval int // heartbeat enable heartbeat heartbeat bool // deregisterCriticalServiceAfter time interval in seconds deregisterCriticalServiceAfter int // serviceChecks user custom checks serviceChecks api.AgentServiceChecks // tags is service tags tags []string // used to control heartbeat lock sync.RWMutex cancelers map[string]*canceler } type canceler struct { ctx context.Context cancel context.CancelFunc done chan struct{} } func defaultResolver(_ context.Context, entries []*api.ServiceEntry) []*registry.ServiceInstance { services := make([]*registry.ServiceInstance, 0, len(entries)) for _, entry := range entries { var version string for _, tag := range entry.Service.Tags { ss := strings.SplitN(tag, "=", 2) if len(ss) == 2 && ss[0] == "version" { version = ss[1] } } endpoints := make([]string, 0) for scheme, addr := range entry.Service.TaggedAddresses { if scheme == "lan_ipv4" || scheme == "wan_ipv4" || scheme == "lan_ipv6" || scheme == "wan_ipv6" { continue } endpoints = append(endpoints, addr.Address) } if len(endpoints) == 0 && entry.Service.Address != "" && entry.Service.Port != 0 { endpoints = append(endpoints, "http://"+net.JoinHostPort(entry.Service.Address, strconv.FormatUint(uint64(entry.Service.Port), 10))) } services = append(services, ®istry.ServiceInstance{ ID: entry.Service.ID, Name: entry.Service.Service, Metadata: entry.Service.Meta, Version: version, Endpoints: endpoints, }) } return services } // ServiceResolver is used to resolve service endpoints type ServiceResolver func(ctx context.Context, entries []*api.ServiceEntry) []*registry.ServiceInstance // Service get services from consul func (c *Client) Service(ctx context.Context, service string, index uint64, passingOnly bool) ([]*registry.ServiceInstance, uint64, error) { if c.dc == MultiDatacenter { return c.multiDCService(ctx, service, index, passingOnly) } opts := &api.QueryOptions{ WaitIndex: index, WaitTime: time.Second * 55, Datacenter: string(c.dc), } opts = opts.WithContext(ctx) if c.dc == SingleDatacenter { opts.Datacenter = "" } entries, meta, err := c.singleDCEntries(service, "", passingOnly, opts) if err != nil { return nil, 0, err } return c.resolver(ctx, entries), meta.LastIndex, nil } func (c *Client) multiDCService(ctx context.Context, service string, index uint64, passingOnly bool) ([]*registry.ServiceInstance, uint64, error) { opts := &api.QueryOptions{ WaitIndex: index, WaitTime: time.Second * 55, } opts = opts.WithContext(ctx) var instances []*registry.ServiceInstance dcs, err := c.cli.Catalog().Datacenters() if err != nil { return nil, 0, err } for _, dc := range dcs { opts.Datacenter = dc e, m, err := c.singleDCEntries(service, "", passingOnly, opts) if err != nil { return nil, 0, err } ins := c.resolver(ctx, e) for _, in := range ins { if in.Metadata == nil { in.Metadata = make(map[string]string, 1) } in.Metadata["dc"] = dc } instances = append(instances, ins...) opts.WaitIndex = m.LastIndex } return instances, opts.WaitIndex, nil } func (c *Client) singleDCEntries(service, tag string, passingOnly bool, opts *api.QueryOptions) ([]*api.ServiceEntry, *api.QueryMeta, error) { return c.cli.Health().Service(service, tag, passingOnly, opts) } // Register register service instance to consul func (c *Client) Register(ctx context.Context, svc *registry.ServiceInstance, enableHealthCheck bool) error { addresses := make(map[string]api.ServiceAddress, len(svc.Endpoints)) checkAddresses := make([]string, 0, len(svc.Endpoints)) for _, endpoint := range svc.Endpoints { raw, err := url.Parse(endpoint) if err != nil { return err } addr := raw.Hostname() port, _ := strconv.ParseUint(raw.Port(), 10, 16) checkAddresses = append(checkAddresses, net.JoinHostPort(addr, strconv.FormatUint(port, 10))) addresses[raw.Scheme] = api.ServiceAddress{Address: endpoint, Port: int(port)} } tags := []string{fmt.Sprintf("version=%s", svc.Version)} if len(c.tags) > 0 { tags = append(tags, c.tags...) } asr := &api.AgentServiceRegistration{ ID: svc.ID, Name: svc.Name, Meta: svc.Metadata, Tags: tags, TaggedAddresses: addresses, } if len(checkAddresses) > 0 { host, portRaw, _ := net.SplitHostPort(checkAddresses[0]) port, _ := strconv.ParseInt(portRaw, 10, 32) asr.Address = host asr.Port = int(port) } if enableHealthCheck { for _, address := range checkAddresses { asr.Checks = append(asr.Checks, &api.AgentServiceCheck{ TCP: address, Interval: fmt.Sprintf("%ds", c.healthcheckInterval), DeregisterCriticalServiceAfter: fmt.Sprintf("%ds", c.deregisterCriticalServiceAfter), Timeout: "5s", }) } // custom checks asr.Checks = append(asr.Checks, c.serviceChecks...) } if c.heartbeat { asr.Checks = append(asr.Checks, &api.AgentServiceCheck{ CheckID: "service:" + svc.ID, TTL: fmt.Sprintf("%ds", c.healthcheckInterval*2), DeregisterCriticalServiceAfter: fmt.Sprintf("%ds", c.deregisterCriticalServiceAfter), }) } c.lock.Lock() if cc, ok := c.cancelers[svc.ID]; ok { cc.cancel() <-cc.done } var cc *canceler if c.heartbeat { cancelCtx, cancel := context.WithCancel(context.Background()) cc = &canceler{ ctx: cancelCtx, cancel: cancel, done: make(chan struct{}), } c.cancelers[svc.ID] = cc go func() { <-cc.done cc.cancel() c.lock.Lock() if c.cancelers[svc.ID] == cc { delete(c.cancelers, svc.ID) } c.lock.Unlock() }() } c.lock.Unlock() err := c.cli.Agent().ServiceRegisterOpts(asr, api.ServiceRegisterOpts{}.WithContext(ctx)) if err != nil { if c.heartbeat { close(cc.done) } return err } if c.heartbeat { go func() { defer close(cc.done) err = c.cli.Agent().UpdateTTL("service:"+svc.ID, "pass", "pass") if err != nil { log.Errorf("[Consul]update ttl heartbeat to consul failed!err:=%v", err) } ticker := time.NewTicker(time.Second * time.Duration(c.healthcheckInterval)) defer ticker.Stop() for { select { case <-cc.ctx.Done(): _ = c.cli.Agent().ServiceDeregister(svc.ID) return case <-ticker.C: err = c.cli.Agent().UpdateTTLOpts("service:"+svc.ID, "pass", "pass", new(api.QueryOptions).WithContext(cc.ctx)) if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { _ = c.cli.Agent().ServiceDeregister(svc.ID) return } if err != nil { log.Errorf("[Consul] update ttl heartbeat to consul failed! err=%v", err) // when the previous report fails, try to re register the service if err := sleepCtx(cc.ctx, time.Duration(rand.IntN(5))*time.Second); err != nil { _ = c.cli.Agent().ServiceDeregister(svc.ID) return } if err := c.cli.Agent().ServiceRegisterOpts(asr, api.ServiceRegisterOpts{}.WithContext(cc.ctx)); err != nil { log.Errorf("[Consul] re registry service failed!, err=%v", err) } else { log.Warn("[Consul] re registry of service occurred success") } } } } }() } return nil } func sleepCtx(ctx context.Context, d time.Duration) error { t := time.NewTimer(d) defer t.Stop() select { case <-ctx.Done(): return ctx.Err() case <-t.C: return nil } } // Deregister service by service ID func (c *Client) Deregister(ctx context.Context, serviceID string) error { c.lock.RLock() cc, ok := c.cancelers[serviceID] c.lock.RUnlock() if ok { cc.cancel() <-cc.done } err := c.cli.Agent().ServiceDeregisterOpts(serviceID, new(api.QueryOptions).WithContext(ctx)) var se api.StatusError if errors.As(err, &se) && se.Code == 404 { // not found err = nil } return err } ================================================ FILE: contrib/registry/consul/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/consul/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/hashicorp/consul/api v1.26.1 ) require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/fatih/color v1.14.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/sys v0.28.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/consul/go.sum ================================================ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/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-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/registry/consul/registry.go ================================================ package consul import ( "context" "fmt" "sync" "sync/atomic" "time" "github.com/hashicorp/consul/api" "github.com/go-kratos/kratos/v2/registry" ) var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) // Option is consul registry option. type Option func(*Registry) // WithHealthCheck with registry health check option. func WithHealthCheck(enable bool) Option { return func(o *Registry) { o.enableHealthCheck = enable } } // WithTimeout with get services timeout option. func WithTimeout(timeout time.Duration) Option { return func(o *Registry) { o.timeout = timeout } } // WithDatacenter with registry datacenter option func WithDatacenter(dc Datacenter) Option { return func(o *Registry) { o.cli.dc = dc } } // WithHeartbeat enable or disable heartbeat func WithHeartbeat(enable bool) Option { return func(o *Registry) { if o.cli != nil { o.cli.heartbeat = enable } } } // WithServiceResolver with endpoint function option. func WithServiceResolver(fn ServiceResolver) Option { return func(o *Registry) { if o.cli != nil { o.cli.resolver = fn } } } // WithHealthCheckInterval with healthcheck interval in seconds. func WithHealthCheckInterval(interval int) Option { return func(o *Registry) { if o.cli != nil { o.cli.healthcheckInterval = interval } } } // WithDeregisterCriticalServiceAfter with deregister-critical-service-after in seconds. func WithDeregisterCriticalServiceAfter(interval int) Option { return func(o *Registry) { if o.cli != nil { o.cli.deregisterCriticalServiceAfter = interval } } } // WithServiceCheck with service checks func WithServiceCheck(checks ...*api.AgentServiceCheck) Option { return func(o *Registry) { if o.cli != nil { o.cli.serviceChecks = checks } } } // WithTags with service tags. func WithTags(tags []string) Option { return func(o *Registry) { if o.cli != nil { o.cli.tags = tags } } } // Config is consul registry config type Config struct { *api.Config } // Registry is consul registry type Registry struct { cli *Client enableHealthCheck bool registry map[string]*serviceSet lock sync.RWMutex timeout time.Duration } // New creates consul registry func New(apiClient *api.Client, opts ...Option) *Registry { r := &Registry{ registry: make(map[string]*serviceSet), enableHealthCheck: true, timeout: 10 * time.Second, cli: &Client{ dc: SingleDatacenter, cli: apiClient, resolver: defaultResolver, healthcheckInterval: 10, heartbeat: true, deregisterCriticalServiceAfter: 600, cancelers: make(map[string]*canceler), }, } for _, o := range opts { o(r) } return r } // Register register service func (r *Registry) Register(ctx context.Context, svc *registry.ServiceInstance) error { return r.cli.Register(ctx, svc, r.enableHealthCheck) } // Deregister deregister service func (r *Registry) Deregister(ctx context.Context, svc *registry.ServiceInstance) error { return r.cli.Deregister(ctx, svc.ID) } // GetService return service by name func (r *Registry) GetService(ctx context.Context, name string) ([]*registry.ServiceInstance, error) { r.lock.RLock() set := r.registry[name] r.lock.RUnlock() getRemote := func() []*registry.ServiceInstance { services, _, err := r.cli.Service(ctx, name, 0, true) if err == nil && len(services) > 0 { return services } return nil } if set == nil { if s := getRemote(); len(s) > 0 { return s, nil } return nil, fmt.Errorf("service %s not resolved in registry", name) } ss, _ := set.services.Load().([]*registry.ServiceInstance) if ss == nil { if s := getRemote(); len(s) > 0 { return s, nil } return nil, fmt.Errorf("service %s not found in registry", name) } return ss, nil } // ListServices return service list. func (r *Registry) ListServices() (allServices map[string][]*registry.ServiceInstance, err error) { r.lock.RLock() defer r.lock.RUnlock() allServices = make(map[string][]*registry.ServiceInstance) for name, set := range r.registry { var services []*registry.ServiceInstance ss, _ := set.services.Load().([]*registry.ServiceInstance) if ss == nil { continue } services = append(services, ss...) allServices[name] = services } return } // Watch resolve service by name func (r *Registry) Watch(ctx context.Context, name string) (registry.Watcher, error) { if err := ctx.Err(); err != nil { return nil, err } r.lock.Lock() set, ok := r.registry[name] if !ok { cancelCtx, cancel := context.WithCancel(context.Background()) set = &serviceSet{ registry: r, watcher: make(map[*watcher]struct{}), services: &atomic.Value{}, serviceName: name, ctx: cancelCtx, cancel: cancel, } r.registry[name] = set } set.ref.Add(1) r.lock.Unlock() // init watcher w := &watcher{ event: make(chan struct{}, 1), } w.ctx, w.cancel = context.WithCancel(ctx) w.set = set set.lock.Lock() set.watcher[w] = struct{}{} set.lock.Unlock() ss, _ := set.services.Load().([]*registry.ServiceInstance) if len(ss) > 0 { // If the service has a value, it needs to be pushed to the watcher, // otherwise the initial data may be blocked forever during the watch. select { case w.event <- struct{}{}: default: } } if !ok { if err := r.resolve(ctx, set); err != nil { return nil, err } } return w, nil } func (r *Registry) resolve(ctx context.Context, ss *serviceSet) error { listServices := r.cli.Service if r.timeout > 0 { listServices = func(ctx context.Context, service string, index uint64, passingOnly bool) ([]*registry.ServiceInstance, uint64, error) { timeoutCtx, cancel := context.WithTimeout(ctx, r.timeout) defer cancel() return r.cli.Service(timeoutCtx, service, index, passingOnly) } } services, idx, err := listServices(ctx, ss.serviceName, 0, true) if err != nil { return err } if len(services) > 0 { ss.broadcast(services) } go func() { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-ticker.C: tmpService, tmpIdx, err := listServices(ss.ctx, ss.serviceName, idx, true) if err != nil { if err := sleepCtx(ss.ctx, time.Second); err != nil { return } continue } if len(tmpService) != 0 && tmpIdx != idx { services = tmpService ss.broadcast(services) } idx = tmpIdx case <-ss.ctx.Done(): return } } }() return nil } func (r *Registry) tryDelete(ss *serviceSet) bool { r.lock.Lock() defer r.lock.Unlock() if ss.ref.Add(-1) != 0 { return false } ss.cancel() delete(r.registry, ss.serviceName) return true } ================================================ FILE: contrib/registry/consul/registry_test.go ================================================ package consul import ( "context" "encoding/json" "fmt" "io" "net" "net/http" "net/http/httptest" "reflect" "strconv" "sync" "testing" "time" "github.com/hashicorp/consul/api" "github.com/go-kratos/kratos/v2/registry" ) func tcpServer(lis net.Listener) { for { conn, err := lis.Accept() if err != nil { return } go func() { _, _ = io.Copy(io.Discard, conn) _ = conn.Close() }() } } func TestRegistry_Register(t *testing.T) { opts := []Option{ WithHealthCheck(false), } type args struct { ctx context.Context serverName string server []*registry.ServiceInstance } test := []struct { name string args args want []*registry.ServiceInstance wantErr bool }{ { name: "normal", args: args{ ctx: context.Background(), serverName: "server-1", server: []*registry.ServiceInstance{ { ID: "1", Name: "server-1", Version: "v0.0.1", Metadata: nil, Endpoints: []string{"http://127.0.0.1:8000"}, }, }, }, want: []*registry.ServiceInstance{ { ID: "1", Name: "server-1", Version: "v0.0.1", Metadata: nil, Endpoints: []string{"http://127.0.0.1:8000"}, }, }, wantErr: false, }, { name: "registry new service replace old service", args: args{ ctx: context.Background(), serverName: "server-1", server: []*registry.ServiceInstance{ { ID: "2", Name: "server-1", Version: "v0.0.1", Metadata: nil, Endpoints: []string{"http://127.0.0.1:8000"}, }, { ID: "2", Name: "server-1", Version: "v0.0.2", Metadata: nil, Endpoints: []string{"http://127.0.0.1:8000"}, }, }, }, want: []*registry.ServiceInstance{ { ID: "2", Name: "server-1", Version: "v0.0.2", Metadata: nil, Endpoints: []string{"http://127.0.0.1:8000"}, }, }, wantErr: false, }, } for _, tt := range test { t.Run(tt.name, func(t *testing.T) { cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500"}) if err != nil { t.Fatalf("create consul client failed: %v", err) } r := New(cli, opts...) for _, instance := range tt.args.server { instance := instance err = r.Register(tt.args.ctx, instance) if err != nil { t.Error(err) } defer func() { err = r.Deregister(tt.args.ctx, instance) if err != nil { t.Error(err) } }() } watchCtx, watchCancel := context.WithCancel(context.Background()) watch, err := r.Watch(watchCtx, tt.args.serverName) if err != nil { t.Error(err) watchCancel() return } got, err := watch.Next() if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", got) watchCancel() return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GetService() got = %v, want %v", got, tt.want) } err = watch.Stop() if err != nil { t.Error(err) } watchCancel() }) } } func TestRegistry_GetService(t *testing.T) { addr := fmt.Sprintf("%s:9091", getIntranetIP()) lis, err := net.Listen("tcp", addr) if err != nil { t.Errorf("listen tcp %s failed!", addr) t.Fail() } defer lis.Close() go tcpServer(lis) time.Sleep(time.Millisecond * 100) cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500"}) if err != nil { t.Fatalf("create consul client failed: %v", err) } opts := []Option{ WithHeartbeat(true), WithHealthCheck(true), WithHealthCheckInterval(5), } r := New(cli, opts...) instance1 := ®istry.ServiceInstance{ ID: "1", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } instance2 := ®istry.ServiceInstance{ ID: "2", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } type fields struct { registry *Registry } type args struct { ctx context.Context serviceName string } tests := []struct { name string fields fields args args want []*registry.ServiceInstance wantErr bool preFunc func(t *testing.T) deferFunc func(t *testing.T) }{ { name: "normal", fields: fields{r}, args: args{ ctx: context.Background(), serviceName: "server-1", }, want: []*registry.ServiceInstance{instance1}, wantErr: false, preFunc: func(t *testing.T) { if err := r.Register(context.Background(), instance1); err != nil { t.Error(err) } watchCtx, watchCancel := context.WithCancel(context.Background()) watch, err := r.Watch(watchCtx, instance1.Name) if err != nil { t.Error(err) } _, err = watch.Next() if err != nil { t.Error(err) } err = watch.Stop() if err != nil { t.Error(err) } watchCancel() }, deferFunc: func(t *testing.T) { err := r.Deregister(context.Background(), instance1) if err != nil { t.Error(err) } }, }, { name: "can't get any", fields: fields{r}, args: args{ ctx: context.Background(), serviceName: "server-x", }, want: nil, wantErr: true, preFunc: func(t *testing.T) { if err := r.Register(context.Background(), instance2); err != nil { t.Error(err) } watchCtx, watchCancel := context.WithCancel(context.Background()) watch, err := r.Watch(watchCtx, instance2.Name) if err != nil { t.Error(err) } _, err = watch.Next() if err != nil { t.Error(err) } err = watch.Stop() if err != nil { t.Error(err) } watchCancel() }, deferFunc: func(t *testing.T) { err := r.Deregister(context.Background(), instance2) if err != nil { t.Error(err) } }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.preFunc != nil { test.preFunc(t) } if test.deferFunc != nil { defer test.deferFunc(t) } service, err := test.fields.registry.GetService(context.Background(), test.args.serviceName) if (err != nil) != test.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, test.wantErr) t.Errorf("GetService() got = %v", service) return } if !reflect.DeepEqual(service, test.want) { t.Errorf("GetService() got = %v, want %v", service, test.want) } }) } } func TestRegistry_Watch(t *testing.T) { addr := fmt.Sprintf("%s:9091", getIntranetIP()) lis, err := net.Listen("tcp", addr) if err != nil { t.Errorf("listen tcp %s failed!", addr) return } defer lis.Close() go tcpServer(lis) cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second}) if err != nil { t.Fatalf("create consul client failed: %v", err) } instance1 := ®istry.ServiceInstance{ ID: "1", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } instance2 := ®istry.ServiceInstance{ ID: "2", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } instance3 := ®istry.ServiceInstance{ ID: "3", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } type args struct { ctx context.Context cancel func() opts []Option instance *registry.ServiceInstance } canceledCtx, cancel := context.WithCancel(context.Background()) tests := []struct { name string args args want []*registry.ServiceInstance wantErr bool preFunc func(t *testing.T) }{ { name: "normal", args: args{ ctx: context.Background(), instance: instance1, opts: []Option{ WithHealthCheck(false), }, }, want: []*registry.ServiceInstance{instance1}, wantErr: false, preFunc: func(*testing.T) {}, }, { name: "ctx has been canceled", args: args{ ctx: canceledCtx, cancel: cancel, instance: instance2, opts: []Option{ WithHealthCheck(false), }, }, want: nil, wantErr: true, preFunc: func(*testing.T) {}, }, { name: "register with healthCheck", args: args{ ctx: context.Background(), instance: instance3, opts: []Option{ WithHeartbeat(true), WithHealthCheck(true), WithHealthCheckInterval(5), }, }, want: []*registry.ServiceInstance{instance3}, wantErr: false, preFunc: func(*testing.T) {}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preFunc != nil { tt.preFunc(t) } r := New(cli, tt.args.opts...) err := r.Register(tt.args.ctx, tt.args.instance) if err != nil { t.Error(err) return } defer func() { err = r.Deregister(context.Background(), tt.args.instance) if err != nil { t.Error(err) } }() watch, err := r.Watch(tt.args.ctx, tt.args.instance.Name) if err != nil { t.Error(err) } if tt.args.cancel != nil { tt.args.cancel() } service, err := watch.Next() if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", service) return } if !reflect.DeepEqual(service, tt.want) { t.Errorf("GetService() got = %v, want %v", service, tt.want) } err = watch.Stop() if err != nil { t.Error(err) } }) } } func TestRegistry_IdleAndWatch(t *testing.T) { addr := fmt.Sprintf("%s:9091", getIntranetIP()) time.Sleep(time.Millisecond * 100) cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second}) if err != nil { t.Fatalf("create consul client failed: %v", err) } r := New(cli, []Option{ WithHealthCheck(false), }...) instance1 := ®istry.ServiceInstance{ ID: "1", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } instance2 := ®istry.ServiceInstance{ ID: "1", Name: "server-1", Version: "v0.0.2", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } type args struct { ctx context.Context instance *registry.ServiceInstance changeInstance *registry.ServiceInstance } tests := []struct { name string args args want1 []*registry.ServiceInstance want2 []*registry.ServiceInstance }{ { name: "many client, one idle", args: args{ ctx: context.Background(), instance: instance1, changeInstance: instance2, }, want1: []*registry.ServiceInstance{instance1}, want2: []*registry.ServiceInstance{instance2}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var watchs []registry.Watcher for i := 0; i < 10; i++ { watch, err := r.Watch(tt.args.ctx, tt.args.instance.Name) //nolint if err != nil { t.Error(err) } defer func() { _ = watch.Stop() }() watchs = append(watchs, watch) } err = r.Register(tt.args.ctx, tt.args.instance) if err != nil { t.Error(err) } defer func() { err = r.Deregister(context.Background(), tt.args.instance) if err != nil { t.Error(err) } }() var wg1 sync.WaitGroup for _, watch := range watchs { wg1.Add(1) go func(watch registry.Watcher, want []*registry.ServiceInstance) { defer wg1.Done() // first service, err := watch.Next() //nolint if err != nil { t.Error(err) return } if !reflect.DeepEqual(service, want) { t.Errorf("GetService() got = %v, want = %v", service, want) return } }(watch, tt.want1) } wg1.Wait() err = r.Register(tt.args.ctx, tt.args.changeInstance) if err != nil { t.Error(err) } defer func() { err := r.Deregister(context.Background(), tt.args.changeInstance) if err != nil { t.Error(err) } }() var wg2 sync.WaitGroup for _, watch := range watchs { wg2.Add(1) go func(watch registry.Watcher, want []*registry.ServiceInstance) { defer wg2.Done() // instance changes service, err := watch.Next() //nolint if err != nil { t.Error(err) return } if !reflect.DeepEqual(service, want) { t.Errorf("GetService() got = %v, want = %v", service, want) } }(watch, tt.want2) } wg2.Wait() }) } } func TestRegistry_IdleAndWatch2(t *testing.T) { addr := fmt.Sprintf("%s:9091", getIntranetIP()) time.Sleep(time.Millisecond * 100) cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second}) if err != nil { t.Fatalf("create consul client failed: %v", err) } instance1 := ®istry.ServiceInstance{ ID: "1", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } type args struct { ctx context.Context opts []Option instance *registry.ServiceInstance } tests := []struct { name string args args want []*registry.ServiceInstance wantErr bool }{ { name: "all clients are idle, create a new one", args: args{ ctx: context.Background(), instance: instance1, opts: []Option{ WithHealthCheck(false), }, }, want: []*registry.ServiceInstance{instance1}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := New(cli, tt.args.opts...) err = r.Register(tt.args.ctx, tt.args.instance) if err != nil { t.Error(err) } defer func() { err = r.Deregister(tt.args.ctx, tt.args.instance) if err != nil { t.Error(err) } }() ctx, cancel := context.WithCancel(context.Background()) for i := 0; i < 10; i++ { stopCtx, stopCancel := context.WithCancel(ctx) watch, err1 := r.Watch(stopCtx, tt.args.instance.Name) if err1 != nil { t.Error(err1) } go func(_ int) { // first service, err2 := watch.Next() if (err2 != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", service) return } }(i) go func() { select { case <-stopCtx.Done(): err1 = watch.Stop() if err1 != nil { t.Errorf("watch stop err:%v", err) } return case <-time.After(time.Minute): stopCancel() err1 = watch.Stop() if err1 != nil { t.Errorf("watch stop err:%v", err) } return } }() } time.Sleep(time.Second * 3) cancel() time.Sleep(time.Second * 2) // Everything is idle. Add new watch. watchCtx, watchCancel := context.WithCancel(context.Background()) watch, err := r.Watch(watchCtx, tt.args.instance.Name) if err != nil { t.Error(err) } service, err := watch.Next() if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", service) watchCancel() return } if !reflect.DeepEqual(service, tt.want) { t.Errorf("GetService() got = %v, want %v", service, tt.want) } watchCancel() }) } } func TestRegistry_ExitOldResolverAndReWatch(t *testing.T) { addr := fmt.Sprintf("%s:9091", getIntranetIP()) time.Sleep(time.Millisecond * 100) cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second}) if err != nil { t.Fatalf("create consul client failed: %v", err) } instance1 := ®istry.ServiceInstance{ ID: "1", Name: "server-1", Version: "v0.0.1", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } instance2 := ®istry.ServiceInstance{ ID: "2", Name: "server-1", Version: "v0.0.2", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, } type args struct { ctx context.Context opts []Option instance *registry.ServiceInstance initialInstance *registry.ServiceInstance } tests := []struct { name string args args want []*registry.ServiceInstance wantErr bool }{ { name: "When it has entered idle mode, but the old resolver has not completely exited, the watch will be re-established due to new requests coming in.", args: args{ ctx: context.Background(), initialInstance: instance1, instance: instance2, opts: []Option{ WithHealthCheck(false), WithTimeout(time.Second * 2), }, }, want: []*registry.ServiceInstance{instance2}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := New(cli, tt.args.opts...) err = r.Register(tt.args.ctx, tt.args.initialInstance) if err != nil { t.Error(err) } // first watch ctx, cancel := context.WithCancel(context.Background()) watch, err := r.Watch(ctx, tt.args.instance.Name) if err != nil { t.Error(err) } service, err := watch.Next() if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", service) } time.Sleep(time.Second * 3) // The simulation entered idle mode first, but the old resolver was not closed yet, and new requests triggered a new Watch. watchCtx := context.Background() // old resolver cancel err = watch.Stop() if err != nil { t.Errorf("watch stop err:%v", err) } cancel() // If it sleeps for a period of time, the old resolve goroutine will exit before the new Watch is processed, and there will be no problems at this time. // time.Sleep(time.Second * 8) newWatch, err := r.Watch(watchCtx, tt.args.instance.Name) if err != nil { t.Error(err) } service, err = newWatch.Next() if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", service) } // change register info time.Sleep(time.Second * 1) err = r.Deregister(tt.args.ctx, tt.args.initialInstance) if err != nil { t.Error(err) } time.Sleep(time.Second * 5) err = r.Register(tt.args.ctx, tt.args.instance) if err != nil { t.Error(err) } defer func() { err = r.Deregister(tt.args.ctx, tt.args.instance) if err != nil { t.Error(err) } }() time.Sleep(time.Second * 2) newWatchCtx, newWatchCancel := context.WithCancel(context.Background()) c := make(chan struct{}, 1) go func() { service, err = newWatch.Next() if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", service) return } if !reflect.DeepEqual(service, tt.want) { t.Errorf("GetService() got = %v, want %v", service, tt.want) } c <- struct{}{} }() time.AfterFunc(time.Second*10, newWatchCancel) select { case <-newWatchCtx.Done(): t.Errorf("Timeout getservice. May be no new resolve goroutine to obtain the latest service information") case <-c: return } }) } } func TestRegistry_ShareServiceSet(t *testing.T) { lastIndex := uint64(0) serviceName := "share-service-set" mux := http.NewServeMux() mux.HandleFunc("/v1/health/service/"+serviceName, func(w http.ResponseWriter, r *http.Request) { var index uint64 if s := r.URL.Query().Get("index"); s != "" { val, err := strconv.ParseUint(s, 10, 64) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } index = val } if index < lastIndex { msg := "repeated request, not the same ServiceSet" http.Error(w, msg, http.StatusBadRequest) t.Error(msg) t.FailNow() return } lastIndex = index + 1 w.Header().Set("X-Consul-Index", strconv.FormatUint(lastIndex, 10)) out := []*api.ServiceEntry{ { Service: &api.AgentService{ ID: "1", Service: serviceName, }, }, } err := json.NewEncoder(w).Encode(out) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) ts := httptest.NewServer(mux) defer ts.Close() cli, err := api.NewClient(&api.Config{Address: ts.URL, WaitTime: 2 * time.Second}) if err != nil { t.Fatalf("create consul client failed: %v", err) } var prev registry.Watcher r := New(cli, WithHealthCheck(false), WithHeartbeat(false)) for i := 0; i < 100; i++ { w, err := r.Watch(context.Background(), serviceName) //nolint if err != nil { t.Error(err) return } // close previous watcher if prev != nil { if err = prev.Stop(); err != nil { t.Error(err) return } } prev = w } time.Sleep(time.Second * 5) if prev != nil { if err = prev.Stop(); err != nil { t.Error(err) return } } } func TestRegistry_MultiWatch(t *testing.T) { cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second}) if err != nil { t.Fatalf("create consul client failed: %v", err) } serviceName := "multi-watch" addr := fmt.Sprintf("%s:9091", getIntranetIP()) instances := []*registry.ServiceInstance{ { ID: "1", Name: serviceName, Version: "v1.0.0", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, }, { ID: "2", Name: serviceName, Version: "v1.0.0", Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)}, }, } r := New(cli, WithHealthCheck(false), WithHeartbeat(true)) err = r.Register(context.Background(), instances[0]) if err != nil { t.Error(err) return } defer func() { err = r.Deregister(context.Background(), instances[0]) if err != nil { t.Error(err) } }() watch1, err := r.Watch(context.Background(), serviceName) if err != nil { t.Error(err) return } defer func() { if err = watch1.Stop(); err != nil { t.Error(err) } }() watch2, err := r.Watch(context.Background(), serviceName) if err != nil { t.Error(err) return } defer func() { if err = watch2.Stop(); err != nil { t.Error(err) } }() got1, err := watch1.Next() if err != nil { t.Error(err) return } got2, err := watch2.Next() if err != nil { t.Error(err) return } if !reflect.DeepEqual(got1, instances[:1]) { t.Errorf("got = %v, want = %v", got1, instances[:1]) return } if !reflect.DeepEqual(got2, instances[:1]) { t.Errorf("got = %v, want = %v", got2, instances[:1]) return } // close first watcher if err = watch1.Stop(); err != nil { t.Error(err) return } // register a new instance err = r.Register(context.Background(), instances[1]) if err != nil { t.Error(err) return } defer func() { err = r.Deregister(context.Background(), instances[1]) if err != nil { t.Error(err) } }() // second watcher should get the new instance got, err := watch2.Next() if err != nil { t.Error(err) return } if !reflect.DeepEqual(got, instances[:2]) { t.Errorf("got = %v, want = %v", got, instances[:2]) return } } func getIntranetIP() string { addrs, err := net.InterfaceAddrs() if err != nil { return "127.0.0.1" } for _, address := range addrs { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { return ipnet.IP.String() } } } return "127.0.0.1" } ================================================ FILE: contrib/registry/consul/service.go ================================================ package consul import ( "context" "sync" "sync/atomic" "github.com/go-kratos/kratos/v2/registry" ) type serviceSet struct { registry *Registry serviceName string watcher map[*watcher]struct{} ref atomic.Int32 services *atomic.Value lock sync.RWMutex // for cancel ctx context.Context cancel context.CancelFunc } func (s *serviceSet) broadcast(ss []*registry.ServiceInstance) { s.services.Store(ss) s.lock.RLock() defer s.lock.RUnlock() for k := range s.watcher { select { case k.event <- struct{}{}: default: } } } func (s *serviceSet) delete(w *watcher) { s.lock.Lock() delete(s.watcher, w) s.lock.Unlock() s.registry.tryDelete(s) } ================================================ FILE: contrib/registry/consul/watcher.go ================================================ package consul import ( "context" "github.com/go-kratos/kratos/v2/registry" ) type watcher struct { event chan struct{} set *serviceSet // for cancel ctx context.Context cancel context.CancelFunc } func (w *watcher) Next() (services []*registry.ServiceInstance, err error) { if err = w.ctx.Err(); err != nil { return } select { case <-w.ctx.Done(): err = w.ctx.Err() return case <-w.event: } ss, ok := w.set.services.Load().([]*registry.ServiceInstance) if ok { services = append(services, ss...) } return } func (w *watcher) Stop() error { if w.cancel != nil { w.cancel() w.cancel = nil w.set.delete(w) } return nil } ================================================ FILE: contrib/registry/discovery/README.md ================================================ ## Discovery Registry This module implements a `registry.Registrar` and `registry.Discovery` interface in kratos based `bilibili/discovery`. [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/go-kratos/kratos/contrib/registry/discovery/v2) ### Quick Start **_Register a service_** ```go import ( "github.com/go-kratos/kratos/contrib/registry/discovery/v2" ) func main() { // initialize a registry r := discovery.New(&discovery.Config{ Nodes: []string{"0.0.0.0:7171"}, Env: "dev", Region: "sh1", Zone: "zone1", Host: "hostname", }) // construct srv instance // ... app := kratos.New( kratos.Name("helloworld"), kratos.Server( httpSrv, grpcSrv, ), kratos.Metadata(map[string]string{"color": "gray"}), // use Registrar kratos.Registrar(r), ) if err := app.Run(); err != nil { log.NewHelper(logger).Fatal(err) } } ``` **_Discover a service_** ```go import ( "github.com/go-kratos/kratos/contrib/registry/discovery/v2" "github.com/go-kratos/kratos/v2/transport/grpc" ) func main() { // initialize a discovery r := discovery.New(&discovery.Config{ Nodes: []string{"0.0.0.0:7171"}, Env: "dev", Region: "sh1", Zone: "zone1", Host: "localhost", }, nil) conn, err := grpc.DialInsecure( context.Background(), grpc.WithEndpoint("discovery:///appid"), // use discovery grpc.WithDiscovery(r), ) if err != nil { log.Fatal(err) } defer conn.Close() // request and log } ``` ### Config explain ```go type Config struct { Nodes []string // discovery nodes address Region string // region of the service, sh Zone string // zone of region, sh001 Env string // env of service, dev, prod and etc Host string // hostname of service } ``` ### References - [bilibili/discovery](https://github.com/bilibili/discovery) ================================================ FILE: contrib/registry/discovery/discovery.go ================================================ package discovery import ( "context" "fmt" "math/rand/v2" "net/url" "strconv" "sync" "sync/atomic" "time" "github.com/go-resty/resty/v2" "github.com/pkg/errors" "github.com/go-kratos/kratos/v2/log" ) type Discovery struct { config *Config once sync.Once ctx context.Context cancelFunc context.CancelFunc httpClient *resty.Client node atomic.Value nodeIdx atomic.Uint64 mutex sync.RWMutex apps map[string]*appInfo registry map[string]struct{} lastHost string cancelPolls context.CancelFunc } type appInfo struct { resolver map[*Resolve]struct{} zoneIns atomic.Value lastTs int64 // latest timestamp } // New construct a Discovery instance which implements registry.Registrar, // registry.Discovery and registry.Watcher. func New(c *Config) *Discovery { if c == nil { c = new(Config) } if err := fixConfig(c); err != nil { panic(err) } ctx, cancel := context.WithCancel(context.Background()) d := &Discovery{ config: c, ctx: ctx, cancelFunc: cancel, apps: map[string]*appInfo{}, registry: map[string]struct{}{}, } d.httpClient = resty.New(). SetTimeout(40 * time.Second) // Discovery self found and watch r := d.resolveBuild(_discoveryAppID) event := r.Watch() _, ok := <-event if !ok { panic("Discovery watch self failed") } discoveryIns, ok := r.fetch(context.Background()) if ok { d.newSelf(discoveryIns.Instances) } go d.selfProc(r, event) return d } // Close stop all running process including Discovery and register func (d *Discovery) Close() error { d.cancelFunc() return nil } // selfProc start a goroutine to refresh Discovery self registration information. func (d *Discovery) selfProc(resolver *Resolve, event <-chan struct{}) { for { _, ok := <-event if !ok { return } zones, ok := resolver.fetch(context.Background()) if ok { d.newSelf(zones.Instances) } } } // newSelf func (d *Discovery) newSelf(zones map[string][]*discoveryInstance) { ins, ok := zones[d.config.Zone] if !ok { return } var nodes []string for _, in := range ins { for _, addr := range in.Addrs { u, err := url.Parse(addr) if err == nil && u.Scheme == "http" { nodes = append(nodes, u.Host) } } } // diff old nodes var olds int if node, ok := d.node.Load().([]string); ok { for _, n := range nodes { for _, o := range node { if o == n { olds++ break } } } } if len(nodes) == olds { return } rand.Shuffle(len(nodes), func(i, j int) { nodes[i], nodes[j] = nodes[j], nodes[i] }) d.node.Store(nodes) } // resolveBuild Discovery resolver builder. func (d *Discovery) resolveBuild(appID string) *Resolve { r := &Resolve{ id: appID, d: d, event: make(chan struct{}, 1), } d.mutex.Lock() app, ok := d.apps[appID] if !ok { app = &appInfo{ resolver: make(map[*Resolve]struct{}), } d.apps[appID] = app cancel := d.cancelPolls if cancel != nil { cancel() } } app.resolver[r] = struct{}{} d.mutex.Unlock() if ok { select { case r.event <- struct{}{}: default: } } log.Debugf("Discovery: AddWatch(%s) already watch(%v)", appID, ok) d.once.Do(func() { go d.serverProc() }) return r } func (d *Discovery) serverProc() { defer log.Debug("Discovery serverProc quit") var ( ctx context.Context cancel context.CancelFunc ) ticker := time.NewTicker(time.Minute * 30) defer ticker.Stop() for { if ctx == nil { ctx, cancel = context.WithCancel(d.ctx) d.mutex.Lock() d.cancelPolls = cancel d.mutex.Unlock() } select { case <-d.ctx.Done(): return case <-ticker.C: d.switchNode() default: } apps, err := d.polls(ctx) if err != nil { d.switchNode() if errors.Is(ctx.Err(), context.Canceled) { ctx = nil continue } time.Sleep(time.Second) continue } d.broadcast(apps) } } func (d *Discovery) pickNode() string { nodes, ok := d.node.Load().([]string) if !ok || len(nodes) == 0 { return d.config.Nodes[rand.IntN(len(d.config.Nodes))] } return nodes[d.nodeIdx.Load()%uint64(len(nodes))] } func (d *Discovery) switchNode() { d.nodeIdx.Add(1) } // renew an instance with Discovery func (d *Discovery) renew(ctx context.Context, ins *discoveryInstance) (err error) { d.mutex.RLock() c := d.config d.mutex.RUnlock() res := new(discoveryCommonResp) uri := fmt.Sprintf(_renewURL, d.pickNode()) // construct parameters to renew p := newParams(d.config) p.Set(_paramKeyAppID, ins.AppID) // send request to Discovery server. if _, err = d.httpClient.R(). SetContext(ctx). SetQueryParamsFromValues(p). SetResult(&res). Post(uri); err != nil { d.switchNode() log.Errorf("Discovery: renew client.Get(%v) env(%s) appid(%s) hostname(%s) error(%v)", uri, c.Env, ins.AppID, c.Host, err) return } if res.Code != _codeOK { err = fmt.Errorf("discovery.renew failed ErrorCode: %d", res.Code) if res.Code == _codeNotFound { if err = d.register(ctx, ins); err != nil { err = errors.Wrap(err, "Discovery.renew instance, and failed to register ins") } return } log.Errorf( "Discovery: renew client.Get(%v) env(%s) appid(%s) hostname(%s) code(%v)", uri, c.Env, ins.AppID, c.Host, res.Code, ) } return } // cancel Remove the registered instance from Discovery func (d *Discovery) cancel(ins *discoveryInstance) (err error) { d.mutex.RLock() config := d.config d.mutex.RUnlock() res := new(discoveryCommonResp) uri := fmt.Sprintf(_cancelURL, d.pickNode()) p := newParams(d.config) p.Set(_paramKeyAppID, ins.AppID) // request // send request to Discovery server. if _, err = d.httpClient.R(). SetContext(context.Background()). SetQueryParamsFromValues(p). SetResult(&res). Post(uri); err != nil { d.switchNode() log.Errorf("Discovery cancel client.Get(%v) env(%s) appid(%s) hostname(%s) error(%v)", uri, config.Env, ins.AppID, config.Host, err) return } // handle response error if res.Code != _codeOK { if res.Code == _codeNotFound { return nil } log.Warnf("Discovery cancel client.Get(%v) env(%s) appid(%s) hostname(%s) code(%v)", uri, config.Env, ins.AppID, config.Host, res.Code) err = fmt.Errorf("ErrorCode: %d", res.Code) return } return } func (d *Discovery) broadcast(apps map[string]*disInstancesInfo) { for appID, v := range apps { var count int // v maybe nil in old version(less than v1.1) Discovery, check in case of panic if v == nil { continue } for zone, ins := range v.Instances { if len(ins) == 0 { delete(v.Instances, zone) } count += len(ins) } if count == 0 { continue } d.mutex.RLock() app, ok := d.apps[appID] d.mutex.RUnlock() if ok { app.lastTs = v.LastTs app.zoneIns.Store(v) d.mutex.RLock() for rs := range app.resolver { select { case rs.event <- struct{}{}: default: } } d.mutex.RUnlock() } } } func (d *Discovery) polls(ctx context.Context) (apps map[string]*disInstancesInfo, err error) { var ( lastTss = make([]int64, 0, 4) appIDs = make([]string, 0, 16) host = d.pickNode() changed bool ) if host != d.lastHost { d.lastHost = host changed = true } d.mutex.RLock() config := d.config for k, v := range d.apps { if changed { v.lastTs = 0 } appIDs = append(appIDs, k) lastTss = append(lastTss, v.lastTs) } d.mutex.RUnlock() // if there is no app, polls just return. if len(appIDs) == 0 { return } uri := fmt.Sprintf(_pollURL, host) res := new(discoveryPollsResp) // params p := newParams(nil) p.Set(_paramKeyEnv, config.Env) p.Set(_paramKeyHostname, config.Host) for _, appID := range appIDs { p.Add(_paramKeyAppID, appID) } for _, ts := range lastTss { p.Add("latest_timestamp", strconv.FormatInt(ts, 10)) } // request reqURI := uri + "?" + p.Encode() if _, err = d.httpClient.R(). SetContext(ctx). SetQueryParamsFromValues(p). SetResult(res).Get(uri); err != nil { d.switchNode() log.Errorf("Discovery: client.Get(%s) error(%+v)", reqURI, err) return nil, err } if res.Code != _codeOK { if res.Code != _codeNotModified { log.Errorf("Discovery: client.Get(%s) get error code(%d)", reqURI, res.Code) } err = fmt.Errorf("discovery.polls failed ErrCode: %d", res.Code) return } for _, app := range res.Data { if app.LastTs == 0 { err = ErrServerError log.Errorf("Discovery: client.Get(%s) latest_timestamp is 0, instances:(%+v)", reqURI, res.Data) return } } log.Debugf("Discovery: successfully polls(%s) instances (%+v)", reqURI, res.Data) apps = res.Data return } // Resolve Discovery resolver. type Resolve struct { id string event chan struct{} d *Discovery } // Watch instance. func (r *Resolve) Watch() <-chan struct{} { return r.event } // fetch resolver instance. func (r *Resolve) fetch(_ context.Context) (ins *disInstancesInfo, ok bool) { r.d.mutex.RLock() app, ok := r.d.apps[r.id] r.d.mutex.RUnlock() if ok { var appIns *disInstancesInfo appIns, ok = app.zoneIns.Load().(*disInstancesInfo) if !ok { return } ins = new(disInstancesInfo) ins.LastTs = appIns.LastTs ins.Scheduler = appIns.Scheduler ins.Instances = make(map[string][]*discoveryInstance, len(appIns.Instances)) for zone, in := range appIns.Instances { ins.Instances[zone] = in } } return } // Close resolver func (r *Resolve) Close() error { r.d.mutex.Lock() if app, ok := r.d.apps[r.id]; ok && len(app.resolver) != 0 { delete(app.resolver, r) // TODO: delete app from builder } r.d.mutex.Unlock() return nil } ================================================ FILE: contrib/registry/discovery/discovery_helper.go ================================================ package discovery import ( "fmt" "net/url" "os" "strconv" "time" "github.com/pkg/errors" "github.com/go-kratos/kratos/v2/registry" ) var ( ErrDuplication = errors.New("register failed: instance duplicated: ") ErrServerError = errors.New("server error") ) const ( // Discovery server resource uri _registerURL = "http://%s/discovery/register" //_setURL = "http://%s/discovery/set" _cancelURL = "http://%s/discovery/cancel" _renewURL = "http://%s/discovery/renew" _pollURL = "http://%s/discovery/polls" // Discovery server error codes _codeOK = 0 _codeNotFound = -404 _codeNotModified = -304 //_SERVER_ERROR = -500 // _registerGap is the gap to renew instance registration. _registerGap = 30 * time.Second _statusUP = "1" _discoveryAppID = "infra.discovery" ) // Config Discovery configures. type Config struct { Nodes []string Region string Zone string Env string Host string } func fixConfig(c *Config) error { if c.Host == "" { c.Host, _ = os.Hostname() } if len(c.Nodes) == 0 || c.Region == "" || c.Zone == "" || c.Env == "" || c.Host == "" { return fmt.Errorf( "invalid Discovery config nodes:%+v region:%s zone:%s deployEnv:%s host:%s", c.Nodes, c.Region, c.Zone, c.Env, c.Host, ) } return nil } // discoveryInstance represents a server the client connects to. type discoveryInstance struct { Region string `json:"region"` // Region is region. Zone string `json:"zone"` // Zone is IDC. Env string `json:"env"` // Env prod/pre/uat/fat1 AppID string `json:"appid"` // AppID is mapping service-tree appId. Hostname string `json:"hostname"` // Hostname is hostname from docker Addrs []string `json:"addrs"` // Addrs is the address of app instance format: scheme://host Version string `json:"version"` // Version is publishing version. LastTs int64 `json:"latest_timestamp"` // LastTs is instance latest updated timestamp // Metadata is the information associated with Addr, which may be used to make load balancing decision. Metadata map[string]string `json:"metadata"` Status int64 `json:"status"` // Status instance status, eg: 1UP 2Waiting } const _reservedInstanceIDKey = "kratos.v2.serviceinstance.id" // fromServerInstance convert registry.ServiceInstance into discoveryInstance func fromServerInstance(ins *registry.ServiceInstance, config *Config) *discoveryInstance { if ins == nil { return nil } metadata := ins.Metadata if ins.Metadata == nil { metadata = make(map[string]string, 8) } metadata[_reservedInstanceIDKey] = ins.ID return &discoveryInstance{ Region: config.Region, Zone: config.Zone, Env: config.Env, AppID: ins.Name, Hostname: config.Host, Addrs: ins.Endpoints, Version: ins.Version, LastTs: time.Now().Unix(), Metadata: metadata, Status: 1, } } // toServiceInstance convert discoveryInstance into registry.ServiceInstance func toServiceInstance(ins *discoveryInstance) *registry.ServiceInstance { if ins == nil { return nil } md := map[string]string{ "region": ins.Region, "zone": ins.Zone, "lastTs": strconv.FormatInt(ins.LastTs, 10), "env": ins.Env, "hostname": ins.Hostname, } if len(ins.Metadata) != 0 { for k, v := range ins.Metadata { md[k] = v } } return ®istry.ServiceInstance{ ID: ins.Metadata[_reservedInstanceIDKey], Name: ins.AppID, Version: ins.Version, Metadata: md, Endpoints: ins.Addrs, } } // disInstancesInfo instance info. type disInstancesInfo struct { Instances map[string][]*discoveryInstance `json:"instances"` LastTs int64 `json:"latest_timestamp"` Scheduler *scheduler `json:"scheduler"` } // scheduler scheduler. type scheduler struct { Clients map[string]*zoneStrategy `json:"clients"` } // zoneStrategy is the scheduling strategy of all zones type zoneStrategy struct { Zones map[string]*strategy `json:"zones"` } // strategy is zone scheduling strategy. type strategy struct { Weight int64 `json:"weight"` } const ( _paramKeyRegion = "region" _paramKeyZone = "zone" _paramKeyEnv = "env" _paramKeyHostname = "hostname" _paramKeyAppID = "appid" _paramKeyAddrs = "addrs" _paramKeyVersion = "version" _paramKeyStatus = "status" _paramKeyMetadata = "metadata" ) func newParams(c *Config) url.Values { p := make(url.Values, 8) if c == nil { return p } p.Set(_paramKeyRegion, c.Region) p.Set(_paramKeyZone, c.Zone) p.Set(_paramKeyEnv, c.Env) p.Set(_paramKeyHostname, c.Host) return p } type discoveryCommonResp struct { Code int `json:"code"` Message string `json:"message"` } type discoveryPollsResp struct { Code int `json:"code"` Data map[string]*disInstancesInfo `json:"data"` } ================================================ FILE: contrib/registry/discovery/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/discovery/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/go-resty/resty/v2 v2.11.0 github.com/pkg/errors v0.9.1 ) require golang.org/x/net v0.33.0 // indirect replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/discovery/go.sum ================================================ github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: contrib/registry/discovery/impl_discover.go ================================================ package discovery import ( "context" "fmt" "time" "github.com/pkg/errors" "github.com/go-kratos/kratos/v2/registry" ) func filterInstancesByZone(ins *disInstancesInfo, zone string) []*registry.ServiceInstance { zoneInstance, ok := ins.Instances[zone] if !ok || len(zoneInstance) == 0 { return nil } out := make([]*registry.ServiceInstance, 0, len(zoneInstance)) for _, v := range zoneInstance { if v == nil { continue } out = append(out, toServiceInstance(v)) } return out } func (d *Discovery) GetService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) { r := d.resolveBuild(serviceName) ins, ok := r.fetch(ctx) if !ok { return nil, errors.New("Discovery.GetService fetch failed") } out := filterInstancesByZone(ins, d.config.Zone) if len(out) == 0 { return nil, fmt.Errorf("Discovery.GetService(%s) not found", serviceName) } return out, nil } func (d *Discovery) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { return &watcher{ resolve: d.resolveBuild(serviceName), serviceName: serviceName, cancelCtx: ctx, }, nil } type watcher struct { resolve *Resolve cancelCtx context.Context serviceName string } func (w *watcher) Next() ([]*registry.ServiceInstance, error) { event := w.resolve.Watch() select { case <-event: // change event come case <-w.cancelCtx.Done(): return nil, fmt.Errorf("watch context canceled: %v", w.cancelCtx.Err()) } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() ins, ok := w.resolve.fetch(ctx) if !ok { return nil, errors.New("Discovery.GetService fetch failed") } out := filterInstancesByZone(ins, w.resolve.d.config.Zone) if len(out) == 0 { return nil, fmt.Errorf("Discovery.GetService(%s) not found", w.serviceName) } return out, nil } func (w *watcher) Stop() error { return w.resolve.Close() } ================================================ FILE: contrib/registry/discovery/impl_registrar.go ================================================ package discovery import ( "context" "encoding/json" "fmt" "strconv" "time" "github.com/pkg/errors" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" ) func (d *Discovery) Register(ctx context.Context, service *registry.ServiceInstance) (err error) { ins := fromServerInstance(service, d.config) d.mutex.Lock() if _, ok := d.registry[ins.AppID]; ok { err = errors.Wrap(ErrDuplication, ins.AppID) } else { d.registry[ins.AppID] = struct{}{} } d.mutex.Unlock() if err != nil { return } ctx, cancel := context.WithCancel(d.ctx) if err = d.register(ctx, ins); err != nil { d.mutex.Lock() delete(d.registry, ins.AppID) d.mutex.Unlock() cancel() return } ch := make(chan struct{}, 1) d.cancelFunc = func() { cancel() <-ch } // renew the current register_service go func() { defer log.Warn("Discovery:register_service goroutine quit") ticker := time.NewTicker(_registerGap) defer ticker.Stop() for { select { case <-ticker.C: _ = d.renew(ctx, ins) case <-ctx.Done(): _ = d.cancel(ins) ch <- struct{}{} return } } }() return } // register an instance with Discovery func (d *Discovery) register(ctx context.Context, ins *discoveryInstance) (err error) { d.mutex.RLock() c := d.config d.mutex.RUnlock() var metadata []byte if ins.Metadata != nil { if metadata, err = json.Marshal(ins.Metadata); err != nil { log.Errorf( "Discovery:register instance Marshal metadata(%v) failed!error(%v)", ins.Metadata, err, ) } } res := new(struct { Code int `json:"code"` Message string `json:"message"` }) uri := fmt.Sprintf(_registerURL, d.pickNode()) // params p := newParams(d.config) p.Set(_paramKeyAppID, ins.AppID) for _, addr := range ins.Addrs { p.Add(_paramKeyAddrs, addr) } p.Set(_paramKeyVersion, ins.Version) if ins.Status == 0 { p.Set(_paramKeyStatus, _statusUP) } else { p.Set(_paramKeyStatus, strconv.FormatInt(ins.Status, 10)) } p.Set(_paramKeyMetadata, string(metadata)) // send request to Discovery server. if _, err = d.httpClient.R(). SetContext(ctx). SetQueryParamsFromValues(p). SetResult(&res). Post(uri); err != nil { d.switchNode() log.Errorf("Discovery: register client.Get(%s) zone(%s) env(%s) appid(%s) addrs(%v) error(%v)", uri+"?"+p.Encode(), c.Zone, c.Env, ins.AppID, ins.Addrs, err) return } if res.Code != 0 { err = fmt.Errorf("ErrorCode: %d", res.Code) log.Errorf("Discovery: register client.Get(%v) env(%s) appid(%s) addrs(%v) code(%v)", uri, c.Env, ins.AppID, ins.Addrs, res.Code) } log.Infof( "Discovery: register client.Get(%v) env(%s) appid(%s) addrs(%s) success\n", uri, c.Env, ins.AppID, ins.Addrs, ) return } func (d *Discovery) Deregister(_ context.Context, service *registry.ServiceInstance) error { ins := fromServerInstance(service, d.config) return d.cancel(ins) } ================================================ FILE: contrib/registry/etcd/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/etcd/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 go.etcd.io/etcd/client/v3 v3.5.11 google.golang.org/grpc v1.61.1 ) require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect go.etcd.io/etcd/api/v3 v3.5.11 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.11 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/protobuf v1.33.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/etcd/go.sum ================================================ github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/etcd/api/v3 v3.5.11 h1:B54KwXbWDHyD3XYAwprxNzTe7vlhR69LuBgZnMVvS7E= go.etcd.io/etcd/api/v3 v3.5.11/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= go.etcd.io/etcd/client/pkg/v3 v3.5.11 h1:bT2xVspdiCj2910T0V+/KHcVKjkUrCZVtk8J2JF2z1A= go.etcd.io/etcd/client/pkg/v3 v3.5.11/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= go.etcd.io/etcd/client/v3 v3.5.11 h1:ajWtgoNSZJ1gmS8k+icvPtqsqEav+iUorF7b0qozgUU= go.etcd.io/etcd/client/v3 v3.5.11/go.mod h1:a6xQUEqFJ8vztO1agJh/KQKOMfFI8og52ZconzcDJwE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/registry/etcd/registry.go ================================================ package etcd import ( "context" "fmt" "math/rand/v2" "time" clientv3 "go.etcd.io/etcd/client/v3" "github.com/go-kratos/kratos/v2/registry" ) var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) // Option is etcd registry option. type Option func(o *options) type options struct { ctx context.Context namespace string ttl time.Duration maxRetry int } // Context with registry context. func Context(ctx context.Context) Option { return func(o *options) { o.ctx = ctx } } // Namespace with registry namespace. func Namespace(ns string) Option { return func(o *options) { o.namespace = ns } } // RegisterTTL with register ttl. func RegisterTTL(ttl time.Duration) Option { return func(o *options) { o.ttl = ttl } } func MaxRetry(num int) Option { return func(o *options) { o.maxRetry = num } } // Registry is etcd registry. type Registry struct { opts *options client *clientv3.Client kv clientv3.KV lease clientv3.Lease /* ctxMap is used to store the context cancel function of each service instance. When the service instance is deregistered, the corresponding context cancel function is called to stop the heartbeat. */ ctxMap map[string]*serviceCancel } type serviceCancel struct { service *registry.ServiceInstance cancel context.CancelFunc } // New creates etcd registry func New(client *clientv3.Client, opts ...Option) (r *Registry) { op := &options{ ctx: context.Background(), namespace: "/microservices", ttl: time.Second * 15, maxRetry: 5, } for _, o := range opts { o(op) } return &Registry{ opts: op, client: client, kv: clientv3.NewKV(client), ctxMap: make(map[string]*serviceCancel), } } // Register the registration. func (r *Registry) Register(ctx context.Context, service *registry.ServiceInstance) error { key := r.registerKey(service) value, err := marshal(service) if err != nil { return err } if r.lease != nil { r.lease.Close() } r.lease = clientv3.NewLease(r.client) leaseID, err := r.registerWithKV(ctx, key, value) if err != nil { return err } hctx, cancel := context.WithCancel(r.opts.ctx) r.ctxMap[key] = &serviceCancel{ service: service, cancel: cancel, } go r.heartBeat(hctx, leaseID, key, value) return nil } func (r *Registry) registerKey(service *registry.ServiceInstance) string { return fmt.Sprintf("%s/%s/%s", r.opts.namespace, service.Name, service.ID) } // Deregister the registration. func (r *Registry) Deregister(ctx context.Context, service *registry.ServiceInstance) error { defer func() { if r.lease != nil { r.lease.Close() } }() // cancel heartbeat key := r.registerKey(service) if serviceCancel, ok := r.ctxMap[key]; ok { serviceCancel.cancel() delete(r.ctxMap, key) } _, err := r.client.Delete(ctx, key) return err } // GetService return the service instances in memory according to the service name. func (r *Registry) GetService(ctx context.Context, name string) ([]*registry.ServiceInstance, error) { key := r.serviceKey(name) resp, err := r.kv.Get(ctx, key, clientv3.WithPrefix()) if err != nil { return nil, err } items := make([]*registry.ServiceInstance, 0, len(resp.Kvs)) for _, kv := range resp.Kvs { si, err := unmarshal(kv.Value) if err != nil { return nil, err } if si.Name != name { continue } items = append(items, si) } return items, nil } func (r *Registry) serviceKey(name string) string { return fmt.Sprintf("%s/%s", r.opts.namespace, name) } // Watch creates a watcher according to the service name. func (r *Registry) Watch(ctx context.Context, name string) (registry.Watcher, error) { key := r.serviceKey(name) return newWatcher(ctx, key, name, r.client) } // registerWithKV create a new lease, return current leaseID func (r *Registry) registerWithKV(ctx context.Context, key string, value string) (clientv3.LeaseID, error) { grant, err := r.lease.Grant(ctx, int64(r.opts.ttl.Seconds())) if err != nil { return 0, err } _, err = r.client.Put(ctx, key, value, clientv3.WithLease(grant.ID)) if err != nil { return 0, err } return grant.ID, nil } func (r *Registry) heartBeat(ctx context.Context, leaseID clientv3.LeaseID, key string, value string) { curLeaseID := leaseID kac, err := r.client.KeepAlive(ctx, leaseID) if err != nil { curLeaseID = 0 } randSource := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0)) for { if curLeaseID == 0 { // try to registerWithKV var retreat []int for retryCnt := 0; retryCnt < r.opts.maxRetry; retryCnt++ { if ctx.Err() != nil { return } // prevent infinite blocking idChan := make(chan clientv3.LeaseID, 1) errChan := make(chan error, 1) cancelCtx, cancel := context.WithCancel(ctx) go func() { defer cancel() id, registerErr := r.registerWithKV(cancelCtx, key, value) if registerErr != nil { errChan <- registerErr } else { idChan <- id } }() select { case <-time.After(3 * time.Second): cancel() continue case <-errChan: continue case curLeaseID = <-idChan: } kac, err = r.client.KeepAlive(ctx, curLeaseID) if err == nil { break } retreat = append(retreat, 1< heartbeatRetry { _ = e.registerEndpoint(e.ctx, ep) retryCount = 0 } } } } } func (e *Client) cancelHeartbeat(appID string) { e.lock.Lock() defer e.lock.Unlock() if ch, ok := e.keepalive[appID]; ok { ch <- struct{}{} } } func (e *Client) filterUp(apps ...Application) (res []Instance) { for _, app := range apps { for _, ins := range app.Instance { if ins.Status == statusUp { res = append(res, ins) } } } return } func (e *Client) pickServer(currentTimes int) string { return e.urls[currentTimes%e.maxRetry] } func (e *Client) shuffle() { rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0)). Shuffle(len(e.urls), func(i, j int) { e.urls[i], e.urls[j] = e.urls[j], e.urls[i] }) } func (e *Client) buildAPI(currentTimes int, params ...string) string { if currentTimes == 0 { e.shuffle() } server := e.pickServer(currentTimes) params = append([]string{server, e.eurekaPath}, params...) return strings.Join(params, "/") } func (e *Client) request(ctx context.Context, method string, params []string, input io.Reader, output any, i int) (bool, error) { request, err := http.NewRequestWithContext(ctx, method, e.buildAPI(i, params...), input) if err != nil { return false, err } request.Header.Add("User-Agent", "go-eureka-client") request.Header.Add("Accept", "application/json;charset=UTF-8") request.Header.Add("Content-Type", "application/json;charset=UTF-8") resp, err := e.client.Do(request) if err != nil { return true, err } defer func() { _, _ = io.Copy(io.Discard, resp.Body) _ = resp.Body.Close() }() if output != nil && resp.StatusCode/100 == 2 { data, err := io.ReadAll(resp.Body) if err != nil { return false, err } err = json.Unmarshal(data, output) if err != nil { return false, err } } if resp.StatusCode >= http.StatusBadRequest { return false, fmt.Errorf("response Error %d", resp.StatusCode) } return false, nil } func (e *Client) do(ctx context.Context, method string, params []string, input io.Reader, output any) error { for i := 0; i < e.maxRetry; i++ { retry, err := e.request(ctx, method, params, input, output, i) if retry { continue } if err != nil { return err } return nil } return fmt.Errorf("retry after %d times", e.maxRetry) } ================================================ FILE: contrib/registry/eureka/eureka.go ================================================ package eureka import ( "context" "strings" "sync" "time" ) type subscriber struct { appID string callBack func() } type API struct { cli *Client allInstances map[string][]Instance subscribers map[string]*subscriber refreshInterval time.Duration lock sync.Mutex } func NewAPI(ctx context.Context, client *Client, refreshInterval time.Duration) *API { e := &API{ cli: client, allInstances: make(map[string][]Instance), subscribers: make(map[string]*subscriber), refreshInterval: refreshInterval, } // it is required to broadcast for the first time go e.broadcast() go e.refresh(ctx) return e } func (e *API) refresh(ctx context.Context) { ticker := time.NewTicker(e.refreshInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: e.broadcast() } } } func (e *API) broadcast() { instances := e.cacheAllInstances() if instances == nil { return } for _, subscriber := range e.subscribers { go subscriber.callBack() } e.lock.Lock() e.allInstances = instances e.lock.Unlock() } func (e *API) cacheAllInstances() map[string][]Instance { items := make(map[string][]Instance) instances := e.cli.FetchAllUpInstances(context.Background()) for _, instance := range instances { items[e.ToAppID(instance.App)] = append(items[instance.App], instance) } return items } func (e *API) Register(ctx context.Context, serviceName string, endpoints ...Endpoint) error { appID := e.ToAppID(serviceName) upInstances := make(map[string]struct{}) for _, ins := range e.GetService(ctx, appID) { upInstances[ins.InstanceID] = struct{}{} } for _, ep := range endpoints { if _, ok := upInstances[ep.InstanceID]; !ok { if err := e.cli.Register(ctx, ep); err != nil { return err } go e.cli.Heartbeat(ep) } } return nil } // Deregister ctx is the same as register ctx func (e *API) Deregister(ctx context.Context, endpoints []Endpoint) error { for _, ep := range endpoints { if err := e.cli.Deregister(ctx, ep.AppID, ep.InstanceID); err != nil { return err } } return nil } func (e *API) Subscribe(serverName string, fn func()) error { e.lock.Lock() appID := e.ToAppID(serverName) e.subscribers[appID] = &subscriber{ appID: appID, callBack: fn, } e.lock.Unlock() go e.broadcast() return nil } func (e *API) GetService(ctx context.Context, serverName string) []Instance { appID := e.ToAppID(serverName) if ins, ok := e.allInstances[appID]; ok { return ins } // if not in allInstances of API, you can try to obtain it separately again return e.cli.FetchAppUpInstances(ctx, appID) } func (e *API) Unsubscribe(serverName string) { e.lock.Lock() delete(e.subscribers, e.ToAppID(serverName)) e.lock.Unlock() } func (e *API) ToAppID(serverName string) string { return strings.ToUpper(serverName) } ================================================ FILE: contrib/registry/eureka/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/eureka/v2 go 1.22 require github.com/go-kratos/kratos/v2 v2.9.2 replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/eureka/go.sum ================================================ ================================================ FILE: contrib/registry/eureka/register.go ================================================ package eureka import ( "context" "fmt" "strconv" "strings" "time" "github.com/go-kratos/kratos/v2/registry" ) var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) type Option func(o *Registry) // WithContext with registry context. func WithContext(ctx context.Context) Option { return func(o *Registry) { o.ctx = ctx } } func WithHeartbeat(interval time.Duration) Option { return func(o *Registry) { o.heartbeatInterval = interval } } func WithRefresh(interval time.Duration) Option { return func(o *Registry) { o.refreshInterval = interval } } func WithEurekaPath(path string) Option { return func(o *Registry) { o.eurekaPath = path } } type Registry struct { ctx context.Context api *API heartbeatInterval time.Duration refreshInterval time.Duration eurekaPath string } func New(eurekaUrls []string, opts ...Option) (*Registry, error) { r := &Registry{ ctx: context.Background(), heartbeatInterval: heartbeatTime, refreshInterval: refreshTime, eurekaPath: "eureka/v2", } for _, o := range opts { o(r) } client := NewClient(eurekaUrls, WithHeartbeatInterval(r.heartbeatInterval), WithClientContext(r.ctx), WithNamespace(r.eurekaPath), ) r.api = NewAPI(r.ctx, client, r.refreshInterval) return r, nil } // Register registers the service instance. // The context here is exclusive to each registry instance. func (r *Registry) Register(ctx context.Context, service *registry.ServiceInstance) error { return r.api.Register(ctx, service.Name, r.Endpoints(service)...) } // Deregister deregisters the service instance from Eureka. func (r *Registry) Deregister(ctx context.Context, service *registry.ServiceInstance) error { return r.api.Deregister(ctx, r.Endpoints(service)) } // GetService gets services from Eureka. func (r *Registry) GetService(ctx context.Context, serviceName string) ([]*registry.ServiceInstance, error) { instances := r.api.GetService(ctx, serviceName) items := make([]*registry.ServiceInstance, 0, len(instances)) for _, instance := range instances { items = append(items, ®istry.ServiceInstance{ ID: instance.Metadata["ID"], Name: instance.Metadata["Name"], Version: instance.Metadata["Version"], Endpoints: []string{instance.Metadata["Endpoints"]}, Metadata: instance.Metadata, }) } return items, nil } // Watch creates a watcher for the service. It uses an independent context. func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { return newWatch(ctx, r.api, serviceName) } func (r *Registry) Endpoints(service *registry.ServiceInstance) []Endpoint { res := make([]Endpoint, 0, len(service.Endpoints)) for _, ep := range service.Endpoints { start := strings.Index(ep, "//") end := strings.LastIndex(ep, ":") appID := strings.ToUpper(service.Name) ip := ep[start+2 : end] sport := ep[end+1:] port, _ := strconv.Atoi(sport) securePort := 443 homePageURL := fmt.Sprintf("%s/", ep) statusPageURL := fmt.Sprintf("%s/info", ep) healthCheckURL := fmt.Sprintf("%s/health", ep) instanceID := strings.Join([]string{ip, appID, sport}, ":") metadata := make(map[string]string) if len(service.Metadata) > 0 { metadata = service.Metadata } if s, ok := service.Metadata["securePort"]; ok { securePort, _ = strconv.Atoi(s) } if s, ok := service.Metadata["homePageURL"]; ok { homePageURL = s } if s, ok := service.Metadata["statusPageURL"]; ok { statusPageURL = s } if s, ok := service.Metadata["healthCheckURL"]; ok { healthCheckURL = s } metadata["ID"] = service.ID metadata["Name"] = service.Name metadata["Version"] = service.Version metadata["Endpoints"] = ep metadata["agent"] = "go-eureka-client" res = append(res, Endpoint{ AppID: appID, IP: ip, Port: port, SecurePort: securePort, HomePageURL: homePageURL, StatusPageURL: statusPageURL, HealthCheckURL: healthCheckURL, InstanceID: instanceID, MetaData: metadata, }) } return res } ================================================ FILE: contrib/registry/eureka/register_test.go ================================================ package eureka import ( "context" "fmt" "log" "sync" "testing" "time" "github.com/go-kratos/kratos/v2/registry" ) func TestRegistry(_ *testing.T) { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) s1 := ®istry.ServiceInstance{ ID: "0", Name: "helloworld", Endpoints: []string{"http://127.0.0.1:1111"}, } s2 := ®istry.ServiceInstance{ ID: "0", Name: "helloworld2", Endpoints: []string{"http://127.0.0.1:222"}, } r, _ := New([]string{"https://127.0.0.1:18761"}, WithContext(ctx), WithHeartbeat(time.Second), WithRefresh(time.Second), WithEurekaPath("eureka")) go do(r, s1) go do(r, s2) time.Sleep(time.Second * 20) cancel() time.Sleep(time.Second * 1) } func do(r *Registry, s *registry.ServiceInstance) { w, err := r.Watch(context.Background(), s.Name) if err != nil { log.Fatal(err) } defer func() { _ = w.Stop() }() go func() { for { res, nextErr := w.Next() if nextErr != nil { return } log.Printf("watch: %d", len(res)) for _, r := range res { log.Printf("next: %+v", r) } } }() ctx, cancel := context.WithCancel(context.Background()) if err = r.Register(ctx, s); err != nil { log.Fatal(err) } time.Sleep(time.Second * 10) res, err := r.GetService(ctx, s.Name) if err != nil { log.Fatal(err) } for i, re := range res { log.Printf("first %d re:%v\n", i, re) } if len(res) != 1 && res[0].Name != s.Name { log.Fatalf("not expected: %+v", res) } if err = r.Deregister(ctx, s); err != nil { log.Fatal(err) } cancel() time.Sleep(time.Second * 10) res, err = r.GetService(ctx, s.Name) if err != nil { log.Fatal(err) } for i, re := range res { log.Printf("second %d re:%v\n", i, re) } if len(res) != 0 { log.Fatalf("not expected empty") } } func TestLock(_ *testing.T) { type me struct { lock sync.Mutex } a := &me{} go func() { defer a.lock.Unlock() a.lock.Lock() fmt.Println("This is fmt first.") time.Sleep(time.Second * 5) }() go func() { defer a.lock.Unlock() a.lock.Lock() fmt.Println("This is fmt second.") time.Sleep(time.Second * 5) }() time.Sleep(time.Second * 10) } ================================================ FILE: contrib/registry/eureka/watcher.go ================================================ package eureka import ( "context" "github.com/go-kratos/kratos/v2/registry" ) var _ registry.Watcher = (*watcher)(nil) type watcher struct { ctx context.Context cancel context.CancelFunc cli *API watchChan chan struct{} serverName string } func newWatch(ctx context.Context, cli *API, serverName string) (*watcher, error) { w := &watcher{ ctx: ctx, cli: cli, serverName: serverName, watchChan: make(chan struct{}, 1), } w.ctx, w.cancel = context.WithCancel(ctx) e := w.cli.Subscribe( serverName, func() { w.watchChan <- struct{}{} }, ) return w, e } func (w *watcher) Next() (services []*registry.ServiceInstance, err error) { select { case <-w.ctx.Done(): return nil, w.ctx.Err() case <-w.watchChan: instances := w.cli.GetService(w.ctx, w.serverName) services = make([]*registry.ServiceInstance, 0, len(instances)) for _, instance := range instances { services = append(services, ®istry.ServiceInstance{ ID: instance.Metadata["ID"], Name: instance.Metadata["Name"], Version: instance.Metadata["Version"], Endpoints: []string{instance.Metadata["Endpoints"]}, Metadata: instance.Metadata, }) } return } } func (w *watcher) Stop() error { w.cancel() w.cli.Unsubscribe(w.serverName) return nil } ================================================ FILE: contrib/registry/kubernetes/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/kubernetes/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/json-iterator/go v1.1.12 k8s.io/api v0.24.3 k8s.io/apimachinery v0.24.3 k8s.io/client-go v0.24.3 ) require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful v2.16.0+incompatible // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/swag v0.19.14 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.5 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/imdario/mergo v0.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.60.1 // indirect k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/kubernetes/go.sum ================================================ 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM= github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180906233101-161cd47e91fd/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20180909124046-d0be0721c37e/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY= k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg= k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY= k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= ================================================ FILE: contrib/registry/kubernetes/registry.go ================================================ // Package kuberegistry registry simply implements the Kubernetes-based Registry package kuberegistry import ( "context" "errors" "fmt" "net" "net/url" "os" "strconv" "strings" "time" jsoniter "github.com/json-iterator/go" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" listerv1 "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "github.com/go-kratos/kratos/v2/registry" ) // Defines the key name of specific fields // Kratos needs to cooperate with the following fields to run properly on Kubernetes: // kratos-service-id: define the ID of the service // kratos-service-app: define the name of the service // kratos-service-version: define the version of the service // kratos-service-metadata: define the metadata of the service // kratos-service-protocols: define the protocols of the service // // Example Deployment: /* apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 5 selector: matchLabels: app: nginx template: metadata: labels: app: nginx kratos-service-id: "56991810-c77f-4a95-8190-393efa9c1a61" kratos-service-app: "nginx" kratos-service-version: "v3.5.0" annotations: kratos-service-protocols: | {"80": "http"} kratos-service-metadata: | {"region": "sh", "zone": "sh001", "cluster": "pd"} spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 */ const ( // LabelsKeyServiceID is used to define the ID of the service LabelsKeyServiceID = "kratos-service-id" // LabelsKeyServiceName is used to define the name of the service LabelsKeyServiceName = "kratos-service-app" // LabelsKeyServiceVersion is used to define the version of the service LabelsKeyServiceVersion = "kratos-service-version" // AnnotationsKeyMetadata is used to define the metadata of the service AnnotationsKeyMetadata = "kratos-service-metadata" // AnnotationsKeyProtocolMap is used to define the protocols of the service // Through the value of this field, Kratos can obtain the application layer protocol corresponding to the port // Example value: {"80": "http", "8081": "grpc"} AnnotationsKeyProtocolMap = "kratos-service-protocols" ) // The Registry simply implements service discovery based on Kubernetes // It has not been verified in the production environment and is currently for reference only type Registry struct { clientSet *kubernetes.Clientset informerFactory informers.SharedInformerFactory podInformer cache.SharedIndexInformer podLister listerv1.PodLister stopCh chan struct{} } // NewRegistry is used to initialize the Registry func NewRegistry(clientSet *kubernetes.Clientset, namespace string) *Registry { if namespace == "" { namespace = metav1.NamespaceAll } informerFactory := informers.NewSharedInformerFactoryWithOptions(clientSet, time.Minute*10, informers.WithNamespace(namespace)) podInformer := informerFactory.Core().V1().Pods().Informer() podLister := informerFactory.Core().V1().Pods().Lister() return &Registry{ clientSet: clientSet, informerFactory: informerFactory, podInformer: podInformer, podLister: podLister, stopCh: make(chan struct{}), } } // Register is used to register services // Note that on Kubernetes, it can only be used to update the id/name/version/metadata/protocols of the current service, // but it cannot be used to update node. func (s *Registry) Register(ctx context.Context, service *registry.ServiceInstance) error { // GetMetadata metadataVal, err := marshal(service.Metadata) if err != nil { return err } // Generate ProtocolMap protocolMap, err := getProtocolMapByEndpoints(service.Endpoints) if err != nil { return err } protocolMapVal, err := marshal(protocolMap) if err != nil { return err } patchBytes, err := jsoniter.Marshal(map[string]any{ "metadata": metav1.ObjectMeta{ Labels: map[string]string{ LabelsKeyServiceID: service.ID, LabelsKeyServiceName: service.Name, LabelsKeyServiceVersion: service.Version, }, Annotations: map[string]string{ AnnotationsKeyMetadata: metadataVal, AnnotationsKeyProtocolMap: protocolMapVal, }, }, }) if err != nil { return err } if _, err = s.clientSet. CoreV1(). Pods(GetNamespace()). Patch(ctx, GetPodName(), types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}); err != nil { return err } return nil } // Deregister the registration. func (s *Registry) Deregister(ctx context.Context, _ *registry.ServiceInstance) error { return s.Register(ctx, ®istry.ServiceInstance{ Metadata: map[string]string{}, }) } // GetService return the service instances in memory according to the service name. func (s *Registry) GetService(_ context.Context, name string) ([]*registry.ServiceInstance, error) { pods, err := s.podLister.List(labels.SelectorFromSet(map[string]string{ LabelsKeyServiceName: name, })) if err != nil { return nil, err } ret := make([]*registry.ServiceInstance, 0, len(pods)) for _, pod := range pods { if pod.Status.Phase != corev1.PodRunning { continue } instance, err := getServiceInstanceFromPod(pod) if err != nil { return nil, err } ret = append(ret, instance) } return ret, nil } func (s *Registry) sendLatestInstances(ctx context.Context, name string, announcement chan []*registry.ServiceInstance) { instances, err := s.GetService(ctx, name) if err != nil { panic(err) } announcement <- instances } // Watch creates a watcher according to the service name. func (s *Registry) Watch(ctx context.Context, name string) (registry.Watcher, error) { stopCh := make(chan struct{}, 1) announcement := make(chan []*registry.ServiceInstance, 1) s.podInformer.AddEventHandler(cache.FilteringResourceEventHandler{ FilterFunc: func(obj any) bool { select { case <-stopCh: return false case <-s.stopCh: return false default: pod := obj.(*corev1.Pod) val := pod.GetLabels()[LabelsKeyServiceName] return val == name } }, Handler: cache.ResourceEventHandlerFuncs{ AddFunc: func(any) { s.sendLatestInstances(ctx, name, announcement) }, UpdateFunc: func(any, any) { s.sendLatestInstances(ctx, name, announcement) }, DeleteFunc: func(any) { s.sendLatestInstances(ctx, name, announcement) }, }, }) return NewIterator(announcement, stopCh), nil } // Start is used to start the Registry // It is non-blocking func (s *Registry) Start() { s.informerFactory.Start(s.stopCh) if !cache.WaitForCacheSync(s.stopCh, s.podInformer.HasSynced) { return } } // Close is used to close the Registry // After closing, any callbacks generated by Watch will not be executed func (s *Registry) Close() { select { case <-s.stopCh: default: close(s.stopCh) } } // //////////// K8S Runtime //////////// // ServiceAccountNamespacePath defines the location of the namespace file const ServiceAccountNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" var currentNamespace = LoadNamespace() // LoadNamespace is used to get the current namespace from the file func LoadNamespace() string { data, err := os.ReadFile(ServiceAccountNamespacePath) if err != nil { return "" } return string(data) } // GetNamespace is used to get the namespace of the Pod where the current container is located func GetNamespace() string { return currentNamespace } // GetPodName is used to get the name of the Pod where the current container is located func GetPodName() string { return os.Getenv("HOSTNAME") } // //////////// ProtocolMap //////////// type protocolMap map[string]string func (m protocolMap) GetProtocol(port int32) string { return m[strconv.Itoa(int(port))] } // //////////// Iterator //////////// // Iterator performs the conversion from channel to iterator // It reads the latest changes from the `chan []*registry.ServiceInstance` // And the outside can sense the closure of Iterator through stopCh type Iterator struct { ch chan []*registry.ServiceInstance stopCh chan struct{} } // NewIterator is used to initialize Iterator func NewIterator(channel chan []*registry.ServiceInstance, stopCh chan struct{}) *Iterator { return &Iterator{ ch: channel, stopCh: stopCh, } } // Next will block until ServiceInstance changes func (iter *Iterator) Next() ([]*registry.ServiceInstance, error) { select { case instances := <-iter.ch: return instances, nil case <-iter.stopCh: return nil, ErrIteratorClosed } } // Stop is used to close the iterator func (iter *Iterator) Stop() error { select { case <-iter.stopCh: default: close(iter.stopCh) } return nil } // //////////// Helper Func //////////// func marshal(in any) (string, error) { return jsoniter.MarshalToString(in) } func unmarshal(data string, in any) error { return jsoniter.UnmarshalFromString(data, in) } func isEmptyObjectString(s string) bool { switch s { case "", "{}", "null", "nil", "[]": return true } return false } func getProtocolMapByEndpoints(endpoints []string) (protocolMap, error) { ret := protocolMap{} for _, endpoint := range endpoints { u, err := url.Parse(endpoint) if err != nil { return nil, err } ret[u.Port()] = u.Scheme } return ret, nil } func getProtocolMapFromPod(pod *corev1.Pod) (protocolMap, error) { protoMap := protocolMap{} if s := pod.Annotations[AnnotationsKeyProtocolMap]; !isEmptyObjectString(s) { err := unmarshal(s, &protoMap) if err != nil { return nil, &ErrorHandleResource{Namespace: pod.Namespace, Name: pod.Name, Reason: err} } } return protoMap, nil } func getMetadataFromPod(pod *corev1.Pod) (map[string]string, error) { metadata := map[string]string{} if s := pod.Annotations[AnnotationsKeyMetadata]; !isEmptyObjectString(s) { err := unmarshal(s, &metadata) if err != nil { return nil, &ErrorHandleResource{Namespace: pod.Namespace, Name: pod.Name, Reason: err} } } return metadata, nil } func getServiceInstanceFromPod(pod *corev1.Pod) (*registry.ServiceInstance, error) { podIP := pod.Status.PodIP podLabels := pod.GetLabels() // Get Metadata metadata, err := getMetadataFromPod(pod) if err != nil { return nil, err } // Get Protocols Definition protocolMap, err := getProtocolMapFromPod(pod) if err != nil { return nil, err } // Get Endpoints var endpoints []string for _, container := range pod.Spec.Containers { for _, cp := range container.Ports { port := cp.ContainerPort protocol := protocolMap.GetProtocol(port) if protocol == "" { if cp.Name != "" { protocol = strings.Split(cp.Name, "-")[0] } else { protocol = string(cp.Protocol) } } addr := protocol + "://" + net.JoinHostPort(podIP, strconv.Itoa(int(port))) endpoints = append(endpoints, addr) } } return ®istry.ServiceInstance{ ID: podLabels[LabelsKeyServiceID], Name: podLabels[LabelsKeyServiceName], Version: podLabels[LabelsKeyServiceVersion], Metadata: metadata, Endpoints: endpoints, }, nil } // //////////// Error Definition //////////// // ErrIteratorClosed defines the error that the iterator is closed var ErrIteratorClosed = errors.New("iterator closed") // ErrorHandleResource defines the error that cannot handle K8S resources normally type ErrorHandleResource struct { Namespace string Name string Reason error } // Error implements the error interface func (err *ErrorHandleResource) Error() string { return fmt.Sprintf("failed to handle resource(namespace=%s, name=%s): %s", err.Namespace, err.Name, err.Reason) } ================================================ FILE: contrib/registry/kubernetes/registry_test.go ================================================ package kuberegistry import ( "context" "os" "path/filepath" "testing" "time" "github.com/go-kratos/kratos/v2/registry" appsv1 "k8s.io/api/apps/v1" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) const ( namespace = "default" deployName = "hello-deployment" podName = "hello" ) var deployment = appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: deployName, }, Spec: appsv1.DeploymentSpec{ Replicas: int32Ptr(1), Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "app": podName, }, }, Template: apiv1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "app": podName, }, }, Spec: apiv1.PodSpec{ Containers: []apiv1.Container{ { Name: "nginx", Image: "nginx:alpine", Ports: []apiv1.ContainerPort{ { Name: "http", Protocol: apiv1.ProtocolTCP, ContainerPort: 80, }, }, Command: []string{ "nginx", "-g", "daemon off;", }, }, }, }, }, }, } func getClientSet() (*kubernetes.Clientset, error) { restConfig, err := rest.InClusterConfig() home := homedir.HomeDir() if err != nil { kubeconfig := filepath.Join(home, ".kube", "config") restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, err } } clientSet, err := kubernetes.NewForConfig(restConfig) if err != nil { return nil, err } return clientSet, nil } func int32Ptr(i int32) *int32 { return &i } func TestSetEnv(t *testing.T) { os.Setenv("HOSTNAME", podName) if os.Getenv("HOSTNAME") != podName { t.Fatal("error") } } func TestRegistry(t *testing.T) { currentNamespace = "default" clientSet, err := getClientSet() if err != nil { t.Fatal(err) } r := NewRegistry(clientSet, currentNamespace) r.Start() svrHello := ®istry.ServiceInstance{ ID: "1", Name: "hello", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:80"}, } _, err = clientSet.AppsV1().Deployments(namespace).Create(context.Background(), &deployment, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } watch, err := r.Watch(context.Background(), svrHello.Name) if err != nil { t.Fatal(err) } defer func() { _ = watch.Stop() }() go func() { for { res, err1 := watch.Next() if err1 != nil { return } t.Logf("watch: %d", len(res)) for _, r := range res { t.Logf("next: %+v", r) } } }() time.Sleep(time.Second) pod, err := clientSet.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ LabelSelector: "app=hello", }) if err != nil { t.Fatal(err) } if len(pod.Items) < 1 { t.Fatal("fetch resource error") } os.Setenv("HOSTNAME", pod.Items[0].Name) // Always remember delete test resource defer func() { _ = clientSet.AppsV1().Deployments(namespace).Delete(context.Background(), deployName, metav1.DeleteOptions{}) }() if err = r.Register(context.Background(), svrHello); err != nil { t.Fatal(err) } time.Sleep(time.Second) res, err := r.GetService(context.Background(), svrHello.Name) if err != nil { t.Fatal(err) } if len(res) != 1 && res[0].Name != svrHello.Name { t.Fatal(err) } if err1 := r.Deregister(context.Background(), svrHello); err1 != nil { t.Fatal(err1) } time.Sleep(time.Second) res, err = r.GetService(context.Background(), svrHello.Name) if err != nil { t.Fatal(err) } if len(res) != 0 { t.Fatal("not expected empty") } } ================================================ FILE: contrib/registry/nacos/README.md ================================================ # Nacos Registry ## example ### server ```go package main import ( "log" "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/contrib/registry/nacos/v2" "github.com/go-kratos/kratos/v2" ) func main() { sc := []constant.ServerConfig{ *constant.NewServerConfig("127.0.0.1", 8848), } client, err := clients.NewNamingClient( vo.NacosClientParam{ ServerConfigs: sc, }, ) if err != nil { log.Panic(err) } r := nacos.New(client) // server app := kratos.New( kratos.Name("helloworld"), kratos.Registrar(r), ) if err := app.Run(); err != nil { log.Fatal(err) } } ``` ### client ```go package main import ( "context" "log" "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/contrib/registry/nacos/v2" "github.com/go-kratos/kratos/v2/transport/grpc" ) func main() { cc := constant.ClientConfig{ NamespaceId: "public", TimeoutMs: 5000, } client, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: &cc, }, ) if err != nil { log.Panic(err) } r := nacos.New(client) // client conn, err := grpc.DialInsecure( context.Background(), grpc.WithEndpoint("discovery:///helloworld"), grpc.WithDiscovery(r), ) defer conn.Close() } ``` ================================================ FILE: contrib/registry/nacos/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/nacos/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/nacos-group/nacos-sdk-go v1.0.9 ) require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect github.com/json-iterator/go v1.1.6 // indirect github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect go.uber.org/atomic v1.6.0 // indirect go.uber.org/multierr v1.5.0 // indirect go.uber.org/zap v1.15.0 // indirect gopkg.in/ini.v1 v1.42.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ replace github.com/buger/jsonparser => github.com/buger/jsonparser v1.1.1 ================================================ FILE: contrib/registry/nacos/go.sum ================================================ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 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/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4= github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nacos-group/nacos-sdk-go v1.0.9 h1:sMvrp6tZj4LdhuHRsS4GCqASB81k3pjmT2ykDQQpwt0= github.com/nacos-group/nacos-sdk-go v1.0.9/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= ================================================ FILE: contrib/registry/nacos/registry.go ================================================ package nacos import ( "context" "errors" "fmt" "maps" "math" "net" "net/url" "strconv" "github.com/nacos-group/nacos-sdk-go/clients/naming_client" "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/v2/registry" ) var ErrServiceInstanceNameEmpty = errors.New("kratos/nacos: ServiceInstance.Name can not be empty") var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) type options struct { prefix string weight float64 cluster string group string kind string } // Option is nacos option. type Option func(o *options) // WithPrefix with prefix path. func WithPrefix(prefix string) Option { return func(o *options) { o.prefix = prefix } } // WithWeight with weight option. func WithWeight(weight float64) Option { return func(o *options) { o.weight = weight } } // WithCluster with cluster option. func WithCluster(cluster string) Option { return func(o *options) { o.cluster = cluster } } // WithGroup with group option. func WithGroup(group string) Option { return func(o *options) { o.group = group } } // WithDefaultKind with default kind option. func WithDefaultKind(kind string) Option { return func(o *options) { o.kind = kind } } // Registry is nacos registry. type Registry struct { opts options cli naming_client.INamingClient } // New new a nacos registry. func New(cli naming_client.INamingClient, opts ...Option) (r *Registry) { op := options{ prefix: "/microservices", cluster: "DEFAULT", group: constant.DEFAULT_GROUP, weight: 100, kind: "grpc", } for _, option := range opts { option(&op) } return &Registry{ opts: op, cli: cli, } } // Register the registration. func (r *Registry) Register(_ context.Context, si *registry.ServiceInstance) error { if si.Name == "" { return ErrServiceInstanceNameEmpty } for _, endpoint := range si.Endpoints { u, err := url.Parse(endpoint) if err != nil { return err } host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } p, err := strconv.Atoi(port) if err != nil { return err } weight := r.opts.weight var rmd map[string]string if si.Metadata == nil { rmd = map[string]string{ "kind": u.Scheme, "version": si.Version, } } else { rmd = maps.Clone(si.Metadata) rmd["kind"] = u.Scheme rmd["version"] = si.Version if w, ok := si.Metadata["weight"]; ok { weight, err = strconv.ParseFloat(w, 64) if err != nil { weight = r.opts.weight } } } _, e := r.cli.RegisterInstance(vo.RegisterInstanceParam{ Ip: host, Port: uint64(p), ServiceName: si.Name + "." + u.Scheme, Weight: weight, Enable: true, Healthy: true, Ephemeral: true, Metadata: rmd, ClusterName: r.opts.cluster, GroupName: r.opts.group, }) if e != nil { return fmt.Errorf("RegisterInstance err %v,%v", e, endpoint) } } return nil } // Deregister the registration. func (r *Registry) Deregister(_ context.Context, service *registry.ServiceInstance) error { for _, endpoint := range service.Endpoints { u, err := url.Parse(endpoint) if err != nil { return err } host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } p, err := strconv.Atoi(port) if err != nil { return err } if _, err = r.cli.DeregisterInstance(vo.DeregisterInstanceParam{ Ip: host, Port: uint64(p), ServiceName: service.Name + "." + u.Scheme, GroupName: r.opts.group, Cluster: r.opts.cluster, Ephemeral: true, }); err != nil { return err } } return nil } // Watch creates a watcher according to the service name. func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { return newWatcher(ctx, r.cli, serviceName, r.opts.group, r.opts.kind, []string{r.opts.cluster}) } // GetService return the service instances in memory according to the service name. func (r *Registry) GetService(_ context.Context, serviceName string) ([]*registry.ServiceInstance, error) { res, err := r.cli.SelectInstances(vo.SelectInstancesParam{ ServiceName: serviceName, GroupName: r.opts.group, HealthyOnly: true, }) if err != nil { return nil, err } items := make([]*registry.ServiceInstance, 0, len(res)) for _, in := range res { kind := r.opts.kind weight := r.opts.weight if k, ok := in.Metadata["kind"]; ok { kind = k } if in.Weight > 0 { weight = in.Weight } r := ®istry.ServiceInstance{ ID: in.InstanceId, Name: in.ServiceName, Version: in.Metadata["version"], Metadata: in.Metadata, Endpoints: []string{kind + "://" + net.JoinHostPort(in.Ip, strconv.Itoa(int(in.Port)))}, } r.Metadata["weight"] = strconv.FormatInt(int64(math.Ceil(weight)), 10) items = append(items, r) } return items, nil } ================================================ FILE: contrib/registry/nacos/registry_test.go ================================================ package nacos import ( "context" "reflect" "testing" "time" "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/v2/registry" ) var testServerConfig = []constant.ServerConfig{ *constant.NewServerConfig("127.0.0.1", 8848), } func TestRegistry_Register(t *testing.T) { sc := testServerConfig cc := constant.ClientConfig{ NamespaceId: "public", // namespace id TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } // a more graceful way to create naming client client, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } r := New(client) testServer := ®istry.ServiceInstance{ ID: "1", Name: "test1", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"}, } testServerWithMetadata := ®istry.ServiceInstance{ ID: "1", Name: "test1", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"}, Metadata: map[string]string{"idc": "shanghai-xs"}, } type fields struct { registry *Registry } type args struct { ctx context.Context service *registry.ServiceInstance } tests := []struct { name string fields fields args args wantErr bool deferFunc func(t *testing.T) }{ { name: "normal", fields: fields{ registry: New(client), }, args: args{ ctx: context.Background(), service: testServer, }, wantErr: false, deferFunc: func(t *testing.T) { err = r.Deregister(context.Background(), testServer) if err != nil { t.Error(err) } }, }, { name: "withMetadata", fields: fields{ registry: New(client), }, args: args{ ctx: context.Background(), service: testServerWithMetadata, }, wantErr: false, deferFunc: func(t *testing.T) { err = r.Deregister(context.Background(), testServerWithMetadata) if err != nil { t.Error(err) } }, }, { name: "error", fields: fields{ registry: New(client), }, args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"}, }, }, wantErr: true, }, { name: "urlError", fields: fields{ registry: New(client), }, args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "test", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, }, }, wantErr: true, }, { name: "portError", fields: fields{ registry: New(client), }, args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "test", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1888"}, }, }, wantErr: true, }, { name: "withCluster", fields: fields{ registry: New(client, WithCluster("test")), }, args: args{ ctx: context.Background(), service: testServer, }, wantErr: false, }, { name: "withGroup", fields: fields{ registry: New(client, WithGroup("TEST_GROUP")), }, args: args{ ctx: context.Background(), service: testServer, }, wantErr: false, }, { name: "withWeight", fields: fields{ registry: New(client, WithWeight(200)), }, args: args{ ctx: context.Background(), service: testServer, }, wantErr: false, }, { name: "withPrefix", fields: fields{ registry: New(client, WithPrefix("test")), }, args: args{ ctx: context.Background(), service: testServer, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := tt.fields.registry if err := r.Register(tt.args.ctx, tt.args.service); (err != nil) != tt.wantErr { t.Errorf("Register error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestRegistry_Deregister(t *testing.T) { testServer := ®istry.ServiceInstance{ ID: "1", Name: "test2", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:8080?isSecure=false"}, } type args struct { ctx context.Context service *registry.ServiceInstance } tests := []struct { name string args args wantErr bool preFunc func(t *testing.T) }{ { name: "normal", args: args{ ctx: context.Background(), service: testServer, }, wantErr: false, preFunc: func(t *testing.T) { sc := testServerConfig cc := constant.ClientConfig{ NamespaceId: "public", // namespace id TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } // a more graceful way to create naming client client, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } r := New(client) err = r.Register(context.Background(), testServer) if err != nil { t.Error(err) } }, }, { name: "error", args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "test", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, }, }, wantErr: true, }, { name: "errorPort", args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "notExist", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.18080"}, }, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sc := testServerConfig cc := constant.ClientConfig{ NamespaceId: "public", // namespace id TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } // a more graceful way to create naming client client, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } r := New(client) if tt.preFunc != nil { tt.preFunc(t) } if err := r.Deregister(tt.args.ctx, tt.args.service); (err != nil) != tt.wantErr { t.Errorf("Deregister error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestRegistry_GetService(t *testing.T) { sc := testServerConfig cc := constant.ClientConfig{ NamespaceId: "public", // namespace id TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } // a more graceful way to create naming client client, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } r := New(client) testServer := ®istry.ServiceInstance{ ID: "1", Name: "test3", Version: "v1.0.0", Endpoints: []string{"grpc://127.0.0.1:8080?isSecure=false"}, } type fields struct { registry *Registry } type args struct { ctx context.Context serviceName string } tests := []struct { name string fields fields args args want []*registry.ServiceInstance wantErr bool preFunc func(t *testing.T) deferFunc func(t *testing.T) }{ { name: "normal", preFunc: func(t *testing.T) { err = r.Register(context.Background(), testServer) if err != nil { t.Error(err) } time.Sleep(time.Second * 3) }, deferFunc: func(t *testing.T) { err = r.Deregister(context.Background(), testServer) if err != nil { t.Error(err) } }, fields: fields{ registry: r, }, args: args{ ctx: context.Background(), serviceName: testServer.Name + "." + "grpc", }, want: []*registry.ServiceInstance{{ ID: "127.0.0.1#8080#DEFAULT#DEFAULT_GROUP@@test3.grpc", Name: "DEFAULT_GROUP@@test3.grpc", Version: "v1.0.0", Metadata: map[string]string{"version": "v1.0.0", "kind": "grpc", "weight": "100"}, Endpoints: []string{"grpc://127.0.0.1:8080"}, }}, wantErr: false, }, { name: "errorNotExist", fields: fields{ registry: r, }, args: args{ ctx: context.Background(), serviceName: "notExist", }, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preFunc != nil { tt.preFunc(t) } if tt.deferFunc != nil { defer tt.deferFunc(t) } r := tt.fields.registry got, err := r.GetService(tt.args.ctx, tt.args.serviceName) if (err != nil) != tt.wantErr { t.Errorf("GetService error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService got = %v", got) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GetService got = %v, want %v", got, tt.want) } }) } } func TestRegistry_Watch(t *testing.T) { sc := testServerConfig cc := constant.ClientConfig{ NamespaceId: "public", // namespace id TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "/tmp/nacos/log", CacheDir: "/tmp/nacos/cache", RotateTime: "1h", MaxAge: 3, LogLevel: "debug", } // a more graceful way to create naming client client, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: &cc, ServerConfigs: sc, }, ) if err != nil { t.Fatal(err) } r := New(client) testServer := ®istry.ServiceInstance{ ID: "1", Name: "test4", Version: "v1.0.0", Endpoints: []string{"grpc://127.0.0.1:8080?isSecure=false"}, } cancelCtx, cancel := context.WithCancel(context.Background()) type fields struct { registry *Registry } type args struct { ctx context.Context serviceName string } tests := []struct { name string fields fields args args wantErr bool want []*registry.ServiceInstance processFunc func(t *testing.T) }{ { name: "normal", fields: fields{ registry: New(client), }, args: args{ ctx: context.Background(), serviceName: testServer.Name + "." + "grpc", }, wantErr: false, want: []*registry.ServiceInstance{{ ID: "127.0.0.1#8080#DEFAULT#DEFAULT_GROUP@@test4.grpc", Name: "DEFAULT_GROUP@@test4.grpc", Version: "v1.0.0", Metadata: map[string]string{"version": "v1.0.0", "kind": "grpc"}, Endpoints: []string{"grpc://127.0.0.1:8080"}, }}, processFunc: func(t *testing.T) { err = r.Register(context.Background(), testServer) if err != nil { t.Error(err) } }, }, { name: "ctxCancel", fields: fields{ registry: r, }, args: args{ ctx: cancelCtx, serviceName: testServer.Name, }, wantErr: true, want: nil, processFunc: func(*testing.T) { cancel() }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := tt.fields.registry watch, err := r.Watch(tt.args.ctx, tt.args.serviceName) if err != nil { t.Error(err) return } defer func() { err = watch.Stop() if err != nil { t.Error(err) } }() _, err = watch.Next() if err != nil { t.Error(err) return } if tt.processFunc != nil { tt.processFunc(t) } want, err := watch.Next() if (err != nil) != tt.wantErr { t.Errorf("Watch error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(want, tt.want) { t.Errorf("Watch watcher = %v, want %v", watch, tt.want) } }) } } ================================================ FILE: contrib/registry/nacos/watcher.go ================================================ package nacos import ( "context" "net" "strconv" "github.com/nacos-group/nacos-sdk-go/clients/naming_client" "github.com/nacos-group/nacos-sdk-go/model" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/go-kratos/kratos/v2/registry" ) var _ registry.Watcher = (*watcher)(nil) type watcher struct { serviceName string clusters []string groupName string ctx context.Context cancel context.CancelFunc watchChan chan struct{} cli naming_client.INamingClient kind string subscribeParam *vo.SubscribeParam } func newWatcher(ctx context.Context, cli naming_client.INamingClient, serviceName, groupName, kind string, clusters []string) (*watcher, error) { w := &watcher{ serviceName: serviceName, clusters: clusters, groupName: groupName, cli: cli, kind: kind, watchChan: make(chan struct{}, 1), } w.ctx, w.cancel = context.WithCancel(ctx) w.subscribeParam = &vo.SubscribeParam{ ServiceName: serviceName, Clusters: clusters, GroupName: groupName, SubscribeCallback: func([]model.SubscribeService, error) { select { case w.watchChan <- struct{}{}: default: } }, } e := w.cli.Subscribe(w.subscribeParam) select { case w.watchChan <- struct{}{}: default: } return w, e } func (w *watcher) Next() ([]*registry.ServiceInstance, error) { select { case <-w.ctx.Done(): return nil, w.ctx.Err() case <-w.watchChan: } res, err := w.cli.GetService(vo.GetServiceParam{ ServiceName: w.serviceName, GroupName: w.groupName, Clusters: w.clusters, }) if err != nil { return nil, err } items := make([]*registry.ServiceInstance, 0, len(res.Hosts)) for _, in := range res.Hosts { kind := w.kind if k, ok := in.Metadata["kind"]; ok { kind = k } items = append(items, ®istry.ServiceInstance{ ID: in.InstanceId, Name: res.Name, Version: in.Metadata["version"], Metadata: in.Metadata, Endpoints: []string{kind + "://" + net.JoinHostPort(in.Ip, strconv.Itoa(int(in.Port)))}, }) } return items, nil } func (w *watcher) Stop() error { err := w.cli.Unsubscribe(w.subscribeParam) w.cancel() return err } ================================================ FILE: contrib/registry/polaris/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/polaris/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/polarismesh/polaris-go v1.3.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.4.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/natefinch/lumberjack v2.0.0+incompatible // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.21.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/polaris/go.sum ================================================ 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/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/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 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/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= github.com/gonum/integrate v0.0.0-20181209220457-a422b5c0fdf2/go.mod h1:pDgmNM6seYpwvPos3q+zxlXMsbve6mOIPucUnUOrI7Y= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/polarismesh/polaris-go v1.3.0 h1:KZKX//ow4OPPoS5+s7h07ptprg+2AcNVGrN6WakC9QM= github.com/polarismesh/polaris-go v1.3.0/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20181114220301-adae6a3d119a/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220504150022-98cd25cafc72/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: contrib/registry/polaris/registry.go ================================================ package polaris import ( "context" "net" "net/url" "strconv" "strings" "time" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" "github.com/polarismesh/polaris-go/api" "github.com/polarismesh/polaris-go/pkg/config" "github.com/polarismesh/polaris-go/pkg/model" ) var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) // _instanceIDSeparator . Instance id Separator. const _instanceIDSeparator = "-" type options struct { // required, namespace in polaris Namespace string // required, service access token ServiceToken string // optional, protocol in polaris. Default value is nil, it means use protocol config in service Protocol *string // service weight in polaris. Default value is 100, 0 <= weight <= 10000 Weight int // service priority. Default value is 0. The smaller the value, the lower the priority Priority int // To show service is healthy or not. Default value is True . Healthy bool // Heartbeat enable .Not in polaris . Default value is True. Heartbeat bool // To show service is isolate or not. Default value is False . Isolate bool // TTL timeout. if node needs to use heartbeat to report,required. If not set,server will throw ErrorCode-400141 TTL int // optional, Timeout for single query. Default value is global config // Total is (1+RetryCount) * Timeout Timeout time.Duration // optional, retry count. Default value is global config RetryCount int } // Option is polaris option. type Option func(o *options) // Registry is polaris registry. type Registry struct { opt options provider api.ProviderAPI consumer api.ConsumerAPI } // WithNamespace with Namespace option. func WithNamespace(namespace string) Option { return func(o *options) { o.Namespace = namespace } } // WithServiceToken with ServiceToken option. func WithServiceToken(serviceToken string) Option { return func(o *options) { o.ServiceToken = serviceToken } } // WithProtocol with Protocol option. func WithProtocol(protocol string) Option { return func(o *options) { o.Protocol = &protocol } } // WithWeight with Weight option. func WithWeight(weight int) Option { return func(o *options) { o.Weight = weight } } // WithHealthy with Healthy option. func WithHealthy(healthy bool) Option { return func(o *options) { o.Healthy = healthy } } // WithIsolate with Isolate option. func WithIsolate(isolate bool) Option { return func(o *options) { o.Isolate = isolate } } // WithTTL with TTL option. func WithTTL(TTL int) Option { return func(o *options) { o.TTL = TTL } } // WithTimeout with Timeout option. func WithTimeout(timeout time.Duration) Option { return func(o *options) { o.Timeout = timeout } } // WithRetryCount with RetryCount option. func WithRetryCount(retryCount int) Option { return func(o *options) { o.RetryCount = retryCount } } // WithHeartbeat . with Heartbeat option. func WithHeartbeat(heartbeat bool) Option { return func(o *options) { o.Heartbeat = heartbeat } } func NewRegistry(provider api.ProviderAPI, consumer api.ConsumerAPI, opts ...Option) (r *Registry) { op := options{ Namespace: "default", ServiceToken: "", Protocol: nil, Weight: 0, Priority: 0, Healthy: true, Heartbeat: true, Isolate: false, TTL: 0, Timeout: 0, RetryCount: 0, } for _, option := range opts { option(&op) } return &Registry{ opt: op, provider: provider, consumer: consumer, } } func NewRegistryWithConfig(conf config.Configuration, opts ...Option) (r *Registry) { provider, err := api.NewProviderAPIByConfig(conf) if err != nil { panic(err) } consumer, err := api.NewConsumerAPIByConfig(conf) if err != nil { panic(err) } return NewRegistry(provider, consumer, opts...) } // Register the registration. func (r *Registry) Register(_ context.Context, serviceInstance *registry.ServiceInstance) error { ids := make([]string, 0, len(serviceInstance.Endpoints)) for _, endpoint := range serviceInstance.Endpoints { // get url u, err := url.Parse(endpoint) if err != nil { return err } // get host and port host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } // port to int portNum, err := strconv.Atoi(port) if err != nil { return err } // medata var rmd map[string]string if serviceInstance.Metadata == nil { rmd = map[string]string{ "kind": u.Scheme, "version": serviceInstance.Version, } } else { rmd = make(map[string]string, len(serviceInstance.Metadata)+2) for k, v := range serviceInstance.Metadata { rmd[k] = v } rmd["kind"] = u.Scheme rmd["version"] = serviceInstance.Version } // Register service, err := r.provider.Register( &api.InstanceRegisterRequest{ InstanceRegisterRequest: model.InstanceRegisterRequest{ Service: serviceInstance.Name + u.Scheme, ServiceToken: r.opt.ServiceToken, Namespace: r.opt.Namespace, Host: host, Port: portNum, Protocol: r.opt.Protocol, Weight: &r.opt.Weight, Priority: &r.opt.Priority, Version: &serviceInstance.Version, Metadata: rmd, Healthy: &r.opt.Healthy, Isolate: &r.opt.Isolate, TTL: &r.opt.TTL, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, }, }) if err != nil { return err } instanceID := service.InstanceID if r.opt.Heartbeat { // start heartbeat report go func() { ticker := time.NewTicker(time.Second * time.Duration(r.opt.TTL)) defer ticker.Stop() for { <-ticker.C err = r.provider.Heartbeat(&api.InstanceHeartbeatRequest{ InstanceHeartbeatRequest: model.InstanceHeartbeatRequest{ Service: serviceInstance.Name + u.Scheme, Namespace: r.opt.Namespace, Host: host, Port: portNum, ServiceToken: r.opt.ServiceToken, InstanceID: instanceID, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, }, }) if err != nil { log.Error(err.Error()) continue } } }() } ids = append(ids, instanceID) } // need to set InstanceID for Deregister serviceInstance.ID = strings.Join(ids, _instanceIDSeparator) return nil } // Deregister the registration. func (r *Registry) Deregister(_ context.Context, serviceInstance *registry.ServiceInstance) error { split := strings.Split(serviceInstance.ID, _instanceIDSeparator) for i, endpoint := range serviceInstance.Endpoints { // get url u, err := url.Parse(endpoint) if err != nil { return err } // get host and port host, port, err := net.SplitHostPort(u.Host) if err != nil { return err } // port to int portNum, err := strconv.Atoi(port) if err != nil { return err } // Deregister err = r.provider.Deregister( &api.InstanceDeRegisterRequest{ InstanceDeRegisterRequest: model.InstanceDeRegisterRequest{ Service: serviceInstance.Name + u.Scheme, ServiceToken: r.opt.ServiceToken, Namespace: r.opt.Namespace, InstanceID: split[i], Host: host, Port: portNum, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, }, }, ) if err != nil { return err } } return nil } // GetService return the service instances in memory according to the service name. func (r *Registry) GetService(_ context.Context, serviceName string) ([]*registry.ServiceInstance, error) { // get all instances instancesResponse, err := r.consumer.GetAllInstances(&api.GetAllInstancesRequest{ GetAllInstancesRequest: model.GetAllInstancesRequest{ Service: serviceName, Namespace: r.opt.Namespace, Timeout: &r.opt.Timeout, RetryCount: &r.opt.RetryCount, }, }) if err != nil { return nil, err } serviceInstances := instancesToServiceInstances(instancesResponse.GetInstances()) return serviceInstances, nil } // Watch creates a watcher according to the service name. func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { return newWatcher(ctx, r.opt.Namespace, serviceName, r.consumer) } type Watcher struct { ServiceName string Namespace string Ctx context.Context Cancel context.CancelFunc Channel <-chan model.SubScribeEvent ServiceInstances []*registry.ServiceInstance first bool } func newWatcher(ctx context.Context, namespace string, serviceName string, consumer api.ConsumerAPI) (*Watcher, error) { watchServiceResponse, err := consumer.WatchService(&api.WatchServiceRequest{ WatchServiceRequest: model.WatchServiceRequest{ Key: model.ServiceKey{ Namespace: namespace, Service: serviceName, }, }, }) if err != nil { return nil, err } w := &Watcher{ Namespace: namespace, ServiceName: serviceName, first: true, Channel: watchServiceResponse.EventChannel, ServiceInstances: instancesToServiceInstances(watchServiceResponse.GetAllInstancesResp.GetInstances()), } w.Ctx, w.Cancel = context.WithCancel(ctx) return w, nil } // Next returns services in the following two cases: // 1.the first time to watch and the service instance list is not empty. // 2.any service instance changes found. // if the above two conditions are not met, it will block until context deadline exceeded or canceled func (w *Watcher) Next() ([]*registry.ServiceInstance, error) { if w.first { w.first = false return w.ServiceInstances, nil } select { case <-w.Ctx.Done(): return nil, w.Ctx.Err() case event := <-w.Channel: if event.GetSubScribeEventType() == model.EventInstance { // this always true, but we need to check it to make sure EventType not change if instanceEvent, ok := event.(*model.InstanceEvent); ok { // handle DeleteEvent if instanceEvent.DeleteEvent != nil { for _, instance := range instanceEvent.DeleteEvent.Instances { for i, serviceInstance := range w.ServiceInstances { if serviceInstance.ID == instance.GetId() { // remove equal if len(w.ServiceInstances) <= 1 { w.ServiceInstances = w.ServiceInstances[:0] continue } w.ServiceInstances = append(w.ServiceInstances[:i], w.ServiceInstances[i+1:]...) } } } } // handle UpdateEvent if instanceEvent.UpdateEvent != nil { for i, serviceInstance := range w.ServiceInstances { for _, update := range instanceEvent.UpdateEvent.UpdateList { if serviceInstance.ID == update.Before.GetId() { w.ServiceInstances[i] = instanceToServiceInstance(update.After) } } } } // handle AddEvent if instanceEvent.AddEvent != nil { w.ServiceInstances = append(w.ServiceInstances, instancesToServiceInstances(instanceEvent.AddEvent.Instances)...) } } return w.ServiceInstances, nil } } return w.ServiceInstances, nil } // Stop close the watcher. func (w *Watcher) Stop() error { w.Cancel() return nil } func instancesToServiceInstances(instances []model.Instance) []*registry.ServiceInstance { serviceInstances := make([]*registry.ServiceInstance, 0, len(instances)) for _, instance := range instances { if instance.IsHealthy() { serviceInstances = append(serviceInstances, instanceToServiceInstance(instance)) } } return serviceInstances } func instanceToServiceInstance(instance model.Instance) *registry.ServiceInstance { metadata := instance.GetMetadata() // Usually, it won't fail in kratos if register correctly kind := "" if k, ok := metadata["kind"]; ok { kind = k } return ®istry.ServiceInstance{ ID: instance.GetId(), Name: instance.GetService(), Version: metadata["version"], Metadata: metadata, Endpoints: []string{kind + "://" + net.JoinHostPort(instance.GetHost(), strconv.Itoa(int(instance.GetPort())))}, } } ================================================ FILE: contrib/registry/servicecomb/README.md ================================================ # Servicecomb Registry ## example ### server ```go package main import ( "log" "github.com/go-chassis/sc-client" "github.com/go-kratos/kratos/contrib/registry/servicecomb/v2" "github.com/go-kratos/kratos/v2" ) func main() { c, err := sc.NewClient(sc.Options{ Endpoints: []string{"127.0.0.1:30100"}, }) if err != nil { log.Panic(err) } r := servicecomb.NewRegistry(c) app := kratos.New( kratos.Name("helloServicecomb"), kratos.Registrar(r), ) if err := app.Run(); err != nil { log.Fatal(err) } } ``` ### client ```go package main import ( "context" "log" "github.com/go-chassis/sc-client" "github.com/go-kratos/kratos/contrib/registry/servicecomb/v2" "github.com/go-kratos/kratos/v2/transport/grpc" ) func main() { c, err := sc.NewClient(sc.Options{ Endpoints: []string{"127.0.0.1:30100"}, }) if err != nil { log.Panic(err) } r := servicecomb.NewRegistry(c) ctx := context.Background() conn, err := grpc.DialInsecure( ctx, grpc.WithEndpoint("discovery:///helloServicecomb"), grpc.WithDiscovery(r), ) if err != nil { return } defer conn.Close() } ``` ================================================ FILE: contrib/registry/servicecomb/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/servicecomb/v2 go 1.22 require ( github.com/go-chassis/cari v0.6.0 github.com/go-chassis/sc-client v0.6.1-0.20210615014358-a45e9090c751 github.com/go-kratos/kratos/v2 v2.9.2 github.com/gofrs/uuid v4.2.0+incompatible ) require ( github.com/cenkalti/backoff v2.0.0+incompatible // indirect github.com/go-chassis/foundation v0.4.0 // indirect github.com/go-chassis/openlog v1.1.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7 // indirect golang.org/x/net v0.33.0 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/servicecomb/go.sum ================================================ github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY= github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 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/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/go-chassis/cari v0.4.0/go.mod h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SKa4Avrd8= github.com/go-chassis/cari v0.6.0 h1:cwBchwt9L8JOyO6QkzXFAsseMJ10zVSiVK8eDLD0HkA= github.com/go-chassis/cari v0.6.0/go.mod h1:mSDRCOQXGmlD69A6NG0hsv0UP1xbVPtL6HCGI6X1tqs= github.com/go-chassis/foundation v0.3.0/go.mod h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA= github.com/go-chassis/foundation v0.4.0 h1:z0xETnSxF+vRXWjoIhOdzt6rywjZ4sB++utEl4YgWEY= github.com/go-chassis/foundation v0.4.0/go.mod h1:6NsIUaHghTFRGfCBcZN011zl196F6OR5QvD9N+P4oWU= github.com/go-chassis/openlog v1.1.2/go.mod h1:+eYCADVxWyJkwsFMUBrMxyQlNqW+UUsCxvR2LrYZUaA= github.com/go-chassis/openlog v1.1.3 h1:XqIOvZ8YPJ9o9lLtLBskQNNWolK5kC6a4Sv7r4s9sZ4= github.com/go-chassis/openlog v1.1.3/go.mod h1:+eYCADVxWyJkwsFMUBrMxyQlNqW+UUsCxvR2LrYZUaA= github.com/go-chassis/sc-client v0.6.1-0.20210615014358-a45e9090c751 h1:hpWN/MZBMsnJqXdMkW7v0wsC+4rYulPsBFMrHCmZMQc= github.com/go-chassis/sc-client v0.6.1-0.20210615014358-a45e9090c751/go.mod h1:TBS9g7OaIeu1OR/9tVPJEl6BgHFcYEdbuJlgVDQczbc= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7 h1:L89uC9ATI61/V2eNgZYtQHyjjyjEplemB+aky4HdyzQ= github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/karlseguin/ccache/v2 v2.0.8/go.mod h1:2BDThcfQMf/c0jnZowt16eW405XIqZPavt+HoYEtcxQ= github.com/karlseguin/expect v1.0.2-0.20190806010014-778a5f0c6003/go.mod h1:zNBxMY8P21owkeogJELCLeHIt+voOSduHYTFUbwRAV8= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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/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/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/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-20181030221726-6c7e314b6563/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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/registry/servicecomb/registry.go ================================================ package servicecomb import ( "context" "encoding/json" "os" "time" "github.com/go-chassis/cari/discovery" "github.com/go-chassis/cari/pkg/errsvc" "github.com/go-chassis/sc-client" "github.com/gofrs/uuid" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" ) func init() { appID = os.Getenv(appIDVar) if appID == "" { appID = "default" } env = os.Getenv(envVar) } var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) var ( curServiceID string appID string env string ) const ( appIDKey = "appId" envKey = "environment" envVar = "CAS_ENVIRONMENT_ID" appIDVar = "CAS_APPLICATION_NAME" frameWorkName = "kratos" frameWorkVersion = "v2" ) type RegistryClient interface { GetMicroServiceID(appID, microServiceName, version, env string, opts ...sc.CallOption) (string, error) FindMicroServiceInstances(consumerID, appID, microServiceName, versionRule string, opts ...sc.CallOption) ([]*discovery.MicroServiceInstance, error) RegisterService(microService *discovery.MicroService) (string, error) RegisterMicroServiceInstance(microServiceInstance *discovery.MicroServiceInstance) (string, error) Heartbeat(microServiceID, microServiceInstanceID string) (bool, error) UnregisterMicroServiceInstance(microServiceID, microServiceInstanceID string) (bool, error) WatchMicroService(microServiceID string, callback func(*sc.MicroServiceInstanceChangedEvent)) error } // Registry is servicecomb registry. type Registry struct { cli RegistryClient } func NewRegistry(client RegistryClient) *Registry { r := &Registry{ cli: client, } return r } func (r *Registry) GetService(_ context.Context, serviceName string) ([]*registry.ServiceInstance, error) { instances, err := r.cli.FindMicroServiceInstances("", appID, serviceName, "") if err != nil { return nil, err } svcInstances := make([]*registry.ServiceInstance, 0, len(instances)) for _, instance := range instances { svcInstances = append(svcInstances, ®istry.ServiceInstance{ ID: instance.InstanceId, Name: serviceName, Metadata: instance.Properties, Endpoints: instance.Endpoints, Version: instance.ServiceId, }) } return svcInstances, nil } func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { return newWatcher(ctx, r.cli, serviceName) } func (r *Registry) Register(_ context.Context, svcIns *registry.ServiceInstance) error { fw := &discovery.FrameWork{ Name: frameWorkName, Version: frameWorkVersion, } ms := &discovery.MicroService{ ServiceName: svcIns.Name, AppId: appID, Version: svcIns.Version, Environment: env, Framework: fw, } // attempt to register the microservice sid, err := r.cli.RegisterService(ms) // if it fails, it may indicate that the service is already registered if err != nil { registryException, ok := err.(*sc.RegistryException) if !ok { return err } var svcErr errsvc.Error parseErr := json.Unmarshal([]byte(registryException.Message), &svcErr) if parseErr != nil { return parseErr } // if the error code is not specific to the service already existing, return the current error if svcErr.Code != discovery.ErrServiceAlreadyExists { return err } sid, err = r.cli.GetMicroServiceID(appID, ms.ServiceName, ms.Version, ms.Environment) if err != nil { return err } } else { // save the service ID for the newly registered service curServiceID = sid } if svcIns.ID == "" { var id uuid.UUID id, err = uuid.NewV4() if err != nil { return err } svcIns.ID = id.String() } props := map[string]string{ appIDKey: appID, envKey: env, } _, err = r.cli.RegisterMicroServiceInstance(&discovery.MicroServiceInstance{ InstanceId: svcIns.ID, ServiceId: sid, Endpoints: svcIns.Endpoints, HostName: svcIns.ID, Properties: props, Version: svcIns.Version, }) if err != nil { return err } go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { <-ticker.C _, err = r.cli.Heartbeat(sid, svcIns.ID) if err != nil { log.Errorf("failed to send heartbeat: %v", err) continue } } }() return nil } func (r *Registry) Deregister(_ context.Context, svcIns *registry.ServiceInstance) error { sid, err := r.cli.GetMicroServiceID(appID, svcIns.Name, svcIns.Version, env) if err != nil { return err } _, err = r.cli.UnregisterMicroServiceInstance(sid, svcIns.ID) if err != nil { return err } return nil } ================================================ FILE: contrib/registry/servicecomb/registry_test.go ================================================ package servicecomb import ( "context" "testing" pb "github.com/go-chassis/cari/discovery" "github.com/go-chassis/sc-client" "github.com/gofrs/uuid" "github.com/go-kratos/kratos/v2/registry" ) var r *Registry func init() { r = NewRegistry(&mockClient{}) } type mockClient struct{} func (receiver *mockClient) WatchMicroService(_ string, _ func(*sc.MicroServiceInstanceChangedEvent)) error { return nil } // nolint func (receiver *mockClient) FindMicroServiceInstances(_, _, microServiceName, _ string, _ ...sc.CallOption, ) ([]*pb.MicroServiceInstance, error) { if microServiceName == "KratosServicecomb" { return []*pb.MicroServiceInstance{{}}, nil } return nil, nil } func (receiver *mockClient) RegisterService(_ *pb.MicroService) (string, error) { return "", nil } func (receiver *mockClient) RegisterMicroServiceInstance(_ *pb.MicroServiceInstance) (string, error) { return "", nil } func (receiver *mockClient) Heartbeat(_, _ string) (bool, error) { return true, nil } func (receiver *mockClient) UnregisterMicroServiceInstance(_, _ string) (bool, error) { return true, nil } func (receiver *mockClient) GetMicroServiceID(_, _, _, _ string, _ ...sc.CallOption) (string, error) { return "", nil } func TestRegistry(t *testing.T) { instanceID, err := uuid.NewV4() if err != nil { t.Fatal(err) } svc := ®istry.ServiceInstance{ Name: "KratosServicecomb", Version: "0.0.1", Metadata: map[string]string{"app": "kratos"}, Endpoints: []string{"tcp://127.0.0.1:9000?isSecure=false"}, ID: instanceID.String(), } ctx := context.TODO() t.Run("Register test, expected: success.", func(t *testing.T) { err = r.Register(ctx, svc) if err != nil { t.Error(err) } }) t.Run("GetService test, expected: success.", func(t *testing.T) { var insts []*registry.ServiceInstance insts, err = r.GetService(ctx, svc.Name) if err != nil { t.Fatal(err) } if len(insts) <= 0 { t.Errorf("inst len less than 0") } }) t.Run("Deregister test, expected: success.", func(t *testing.T) { svc.ID = instanceID.String() err = r.Deregister(ctx, svc) if err != nil { t.Error(err) } }) } func TestWatcher(t *testing.T) { instanceID1, err := uuid.NewV4() if err != nil { t.Fatal(err) } svc1 := ®istry.ServiceInstance{ Name: "WatcherTest", Version: "0.0.1", Metadata: map[string]string{"app": "kratos"}, Endpoints: []string{"tcp://127.0.0.1:9000?isSecure=false"}, ID: instanceID1.String(), } ctx := context.TODO() err = r.Register(ctx, svc1) if err != nil { t.Fatal(err) } w, err := r.Watch(ctx, "WatcherTest") if err != nil { t.Fatal(err) } if w == nil { t.Fatal("w is nil") } sbWatcher := w.(*Watcher) t.Run("Watch register event, expected: success", func(t *testing.T) { go sbWatcher.Put(svc1) var instances []*registry.ServiceInstance instances, err = w.Next() if err != nil { t.Fatal(err) } if len(instances) == 0 { t.Errorf("instances is empty") } if instanceID1.String() != instances[0].ID { t.Errorf("expected %v, got %v", instanceID1.String(), instances[0].ID) } }) t.Run("Watch deregister event, expected: success", func(t *testing.T) { // Deregister instance1. err = r.Deregister(ctx, svc1) if err != nil { t.Fatal(err) } go sbWatcher.Put(svc1) var instances []*registry.ServiceInstance instances, err = w.Next() if err != nil { t.Fatal(err) } if len(instances) == 0 { t.Errorf("instances is empty") } if instanceID1.String() != instances[0].ID { t.Errorf("expected %v, got %v", instanceID1.String(), instances[0].ID) } }) t.Run("Stop test, expected: success", func(t *testing.T) { err = w.Stop() if err != nil { t.Error(err) } }) } ================================================ FILE: contrib/registry/servicecomb/watcher.go ================================================ package servicecomb import ( "context" "github.com/go-chassis/sc-client" "github.com/go-kratos/kratos/v2/registry" ) var _ registry.Watcher = (*Watcher)(nil) type Watcher struct { cli RegistryClient ch chan *registry.ServiceInstance } func newWatcher(_ context.Context, cli RegistryClient, serviceName string) (*Watcher, error) { // establish dependency relationship between the current service and the target service for discovery _, err := cli.FindMicroServiceInstances(curServiceID, appID, serviceName, "") if err != nil { return nil, err } w := &Watcher{ cli: cli, ch: make(chan *registry.ServiceInstance), } go func() { watchErr := w.cli.WatchMicroService(curServiceID, func(event *sc.MicroServiceInstanceChangedEvent) { if event.Key.ServiceName != serviceName { return } svcIns := ®istry.ServiceInstance{ ID: event.Instance.InstanceId, Name: event.Key.ServiceName, Version: event.Key.Version, Metadata: event.Instance.Properties, Endpoints: event.Instance.Endpoints, } w.Put(svcIns) }) if watchErr != nil { return } }() return w, nil } // Put only for UT func (w *Watcher) Put(svcIns *registry.ServiceInstance) { w.ch <- svcIns } func (w *Watcher) Next() ([]*registry.ServiceInstance, error) { var svcInstances []*registry.ServiceInstance svcIns := <-w.ch svcInstances = append(svcInstances, svcIns) return svcInstances, nil } func (w *Watcher) Stop() error { close(w.ch) return nil } ================================================ FILE: contrib/registry/zookeeper/go.mod ================================================ module github.com/go-kratos/kratos/contrib/registry/zookeeper/v2 go 1.22 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/go-zookeeper/zk v1.0.3 golang.org/x/sync v0.10.0 ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/registry/zookeeper/go.sum ================================================ github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= ================================================ FILE: contrib/registry/zookeeper/register.go ================================================ package zookeeper import ( "context" "errors" "path" "time" "github.com/go-zookeeper/zk" "golang.org/x/sync/singleflight" "github.com/go-kratos/kratos/v2/registry" ) var ( _ registry.Registrar = (*Registry)(nil) _ registry.Discovery = (*Registry)(nil) ) // Option is etcd registry option. type Option func(o *options) type options struct { namespace string user string password string } // WithRootPath with registry root path. func WithRootPath(path string) Option { return func(o *options) { o.namespace = path } } // WithDigestACL with registry password. func WithDigestACL(user string, password string) Option { return func(o *options) { o.user = user o.password = password } } // Registry is consul registry type Registry struct { opts *options conn *zk.Conn group singleflight.Group } func New(conn *zk.Conn, opts ...Option) *Registry { options := &options{ namespace: "/microservices", } for _, o := range opts { o(options) } return &Registry{ opts: options, conn: conn, } } func (r *Registry) Register(_ context.Context, service *registry.ServiceInstance) error { var ( data []byte err error ) if err = r.ensureName(r.opts.namespace, []byte(""), 0); err != nil { return err } serviceNamePath := path.Join(r.opts.namespace, service.Name) if err = r.ensureName(serviceNamePath, []byte(""), 0); err != nil { return err } if data, err = marshal(service); err != nil { return err } servicePath := path.Join(serviceNamePath, service.ID) if err = r.ensureName(servicePath, data, zk.FlagEphemeral); err != nil { return err } go r.reRegister(servicePath, data) return nil } // Deregister registry service to zookeeper. func (r *Registry) Deregister(ctx context.Context, service *registry.ServiceInstance) error { ch := make(chan error, 1) servicePath := path.Join(r.opts.namespace, service.Name, service.ID) go func() { err := r.conn.Delete(servicePath, -1) ch <- err }() var err error select { case <-ctx.Done(): err = ctx.Err() case err = <-ch: } return err } // GetService get services from zookeeper func (r *Registry) GetService(_ context.Context, serviceName string) ([]*registry.ServiceInstance, error) { instances, err, _ := r.group.Do(serviceName, func() (any, error) { serviceNamePath := path.Join(r.opts.namespace, serviceName) servicesID, _, err := r.conn.Children(serviceNamePath) if err != nil { return nil, err } items := make([]*registry.ServiceInstance, 0, len(servicesID)) for _, service := range servicesID { servicePath := path.Join(serviceNamePath, service) serviceInstanceByte, _, err := r.conn.Get(servicePath) if err != nil { return nil, err } item, err := unmarshal(serviceInstanceByte) if err != nil { return nil, err } items = append(items, item) } return items, nil }) if err != nil { return nil, err } return instances.([]*registry.ServiceInstance), nil } func (r *Registry) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { prefix := path.Join(r.opts.namespace, serviceName) return newWatcher(ctx, prefix, serviceName, r.conn) } // ensureName ensure node exists, if not exist, create and set data func (r *Registry) ensureName(path string, data []byte, flags int32) error { exists, stat, err := r.conn.Exists(path) if err != nil { return err } // ephemeral nodes handling after restart // fixes a race condition if the server crashes without using CreateProtectedEphemeralSequential() if flags&zk.FlagEphemeral == zk.FlagEphemeral { err = r.conn.Delete(path, stat.Version) if err != nil && !errors.Is(err, zk.ErrNoNode) { return err } exists = false } if !exists { if len(r.opts.user) > 0 && len(r.opts.password) > 0 { _, err = r.conn.Create(path, data, flags, zk.DigestACL(zk.PermAll, r.opts.user, r.opts.password)) } else { _, err = r.conn.Create(path, data, flags, zk.WorldACL(zk.PermAll)) } if err != nil { return err } } return nil } // reRegister re-register data node info when bad connection recovered func (r *Registry) reRegister(path string, data []byte) { sessionID := r.conn.SessionID() ticker := time.NewTicker(time.Second) defer ticker.Stop() for range ticker.C { cur := r.conn.SessionID() // sessionID changed if cur > 0 && sessionID != cur { // re-ensureName if err := r.ensureName(path, data, zk.FlagEphemeral); err != nil { return } sessionID = cur } } } ================================================ FILE: contrib/registry/zookeeper/register_test.go ================================================ package zookeeper import ( "context" "reflect" "testing" "time" "github.com/go-zookeeper/zk" "github.com/go-kratos/kratos/v2/registry" ) func TestRegistry_GetService(t *testing.T) { conn, _, err := zk.Connect([]string{"127.0.0.1:2181"}, time.Second*15) if err != nil { t.Fatal(err) return } r := New(conn) svrHello := ®istry.ServiceInstance{ ID: "1", Name: "hello", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, } type fields struct { registry *Registry } type args struct { ctx context.Context serviceName string } tests := []struct { name string fields fields args args want []*registry.ServiceInstance wantErr bool preFunc func(t *testing.T) deferFunc func(t *testing.T) }{ { name: "normal", preFunc: func(t *testing.T) { err = r.Register(context.Background(), svrHello) if err != nil { t.Error(err) } }, deferFunc: func(t *testing.T) { err = r.Deregister(context.Background(), svrHello) if err != nil { t.Error(err) } }, fields: fields{ registry: r, }, args: args{ ctx: context.Background(), serviceName: svrHello.Name, }, want: []*registry.ServiceInstance{svrHello}, wantErr: false, }, { name: "can't get any", fields: fields{ registry: r, }, args: args{ ctx: context.Background(), serviceName: "helloxxx", }, want: nil, wantErr: true, }, { name: "conn close", preFunc: func(*testing.T) { conn.Close() }, fields: fields{ registry: r, }, args: args{ ctx: context.Background(), serviceName: "hello", }, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preFunc != nil { tt.preFunc(t) } if tt.deferFunc != nil { defer tt.deferFunc(t) } r := tt.fields.registry got, err := r.GetService(tt.args.ctx, tt.args.serviceName) if (err != nil) != tt.wantErr { t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("GetService() got = %v", got) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GetService() got = %v, want %v", got, tt.want) } }) } } func TestRegistry_Register(t *testing.T) { conn, _, err := zk.Connect([]string{"127.0.0.1:2181"}, time.Second*15) if err != nil { t.Fatal(err) return } r := New(conn) svrHello := ®istry.ServiceInstance{ ID: "1", Name: "hello", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, } type fields struct { registry *Registry } type args struct { ctx context.Context service *registry.ServiceInstance } tests := []struct { name string fields fields args args wantErr bool preFunc func(t *testing.T) deferFunc func(t *testing.T) }{ { name: "normal", fields: fields{ registry: r, }, args: args{ ctx: context.Background(), service: svrHello, }, wantErr: false, }, { name: "invalid path", fields: fields{ registry: New(conn, WithRootPath("invalid")), }, args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "hello1", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, }, }, wantErr: true, }, { name: "auth", preFunc: func(t *testing.T) { err = conn.AddAuth("digest", []byte("test:test")) if err != nil { t.Error(err) } }, fields: fields{ registry: New(conn, WithRootPath("/tt1"), WithDigestACL("test", "test")), }, args: args{ ctx: context.Background(), service: ®istry.ServiceInstance{ ID: "1", Name: "hello2", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preFunc != nil { tt.preFunc(t) } if tt.deferFunc != nil { defer tt.deferFunc(t) } r := tt.fields.registry if err := r.Register(tt.args.ctx, tt.args.service); (err != nil) != tt.wantErr { t.Errorf("Register() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestRegistry_Deregister(t *testing.T) { conn, _, err := zk.Connect([]string{"127.0.0.1:2181"}, time.Second*15) if err != nil { t.Fatal(err) return } r := New(conn) svrHello := ®istry.ServiceInstance{ ID: "1", Name: "hello", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, } cancelCtx, cancel := context.WithCancel(context.Background()) cancel() type fields struct { registry *Registry } type args struct { ctx context.Context service *registry.ServiceInstance } tests := []struct { name string fields fields args args wantErr bool preFunc func(t *testing.T) deferFunc func(t *testing.T) }{ { name: "normal", fields: fields{ registry: r, }, args: args{ ctx: context.Background(), service: svrHello, }, wantErr: false, preFunc: func(t *testing.T) { err = r.Register(context.Background(), svrHello) if err != nil { t.Error(err) } }, }, { name: "with ctx cancel", fields: fields{ registry: r, }, args: args{ ctx: cancelCtx, service: svrHello, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := tt.fields.registry if err := r.Deregister(tt.args.ctx, tt.args.service); (err != nil) != tt.wantErr { t.Errorf("Deregister() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestRegistry_Watch(t *testing.T) { conn, _, err := zk.Connect([]string{"127.0.0.1:2181"}, time.Second*15) if err != nil { t.Fatal(err) return } closeConn, _, err := zk.Connect([]string{"127.0.0.1:2181"}, time.Second*15) if err != nil { t.Fatal(err) return } r := New(conn) svrHello := ®istry.ServiceInstance{ ID: "1", Name: "hello", Version: "v1.0.0", Endpoints: []string{"127.0.0.1:8080"}, } cancelCtx, cancel := context.WithCancel(context.Background()) type fields struct { registry *Registry } type args struct { ctx context.Context serviceName string } tests := []struct { name string fields fields args args wantErr bool want []*registry.ServiceInstance preFunc func(t *testing.T) deferFunc func(t *testing.T) processFunc func(t *testing.T, w registry.Watcher) }{ { name: "normal", fields: fields{ registry: r, }, args: args{ ctx: context.Background(), serviceName: svrHello.Name, }, wantErr: false, want: []*registry.ServiceInstance{svrHello}, deferFunc: func(t *testing.T) { err = r.Deregister(context.Background(), svrHello) if err != nil { t.Error(err) } }, processFunc: func(t *testing.T, _ registry.Watcher) { err = r.Register(context.Background(), svrHello) if err != nil { t.Error(err) } }, }, { name: "ctx cancel", fields: fields{ registry: r, }, args: args{ ctx: cancelCtx, serviceName: svrHello.Name, }, wantErr: true, want: nil, processFunc: func(*testing.T, registry.Watcher) { cancel() }, }, { name: "disconnect", fields: fields{ registry: New(closeConn), }, args: args{ ctx: context.Background(), serviceName: svrHello.Name, }, wantErr: true, want: nil, processFunc: func(*testing.T, registry.Watcher) { closeConn.Close() }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.preFunc != nil { tt.preFunc(t) } if tt.deferFunc != nil { defer tt.deferFunc(t) } r := tt.fields.registry watcher, err := r.Watch(tt.args.ctx, tt.args.serviceName) if err != nil { t.Error(err) return } defer func() { err = watcher.Stop() if err != nil { t.Error(err) } }() _, err = watcher.Next() if err != nil { t.Error(err) return } if tt.processFunc != nil { tt.processFunc(t, watcher) } want, err := watcher.Next() if (err != nil) != tt.wantErr { t.Errorf("Watch() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(want, tt.want) { t.Errorf("Watch() watcher = %v, want %v", watcher, tt.want) } }) } } ================================================ FILE: contrib/registry/zookeeper/service.go ================================================ package zookeeper import ( "encoding/json" "github.com/go-kratos/kratos/v2/registry" ) func marshal(si *registry.ServiceInstance) ([]byte, error) { return json.Marshal(si) } func unmarshal(data []byte) (si *registry.ServiceInstance, err error) { err = json.Unmarshal(data, &si) return } ================================================ FILE: contrib/registry/zookeeper/watcher.go ================================================ package zookeeper import ( "context" "errors" "path" "sync/atomic" "github.com/go-zookeeper/zk" "github.com/go-kratos/kratos/v2/registry" ) var _ registry.Watcher = (*watcher)(nil) var ErrWatcherStopped = errors.New("watcher stopped") type watcher struct { ctx context.Context event chan zk.Event conn *zk.Conn cancel context.CancelFunc first atomic.Bool // prefix for ZooKeeper paths or keys (used for filtering or identifying watched nodes) prefix string // the name of the service being watched in ZooKeeper serviceName string } func newWatcher(ctx context.Context, prefix, serviceName string, conn *zk.Conn) (*watcher, error) { w := &watcher{conn: conn, event: make(chan zk.Event, 1), prefix: prefix, serviceName: serviceName} w.ctx, w.cancel = context.WithCancel(ctx) go w.watch(w.ctx) return w, nil } func (w *watcher) watch(ctx context.Context) { for { // since a single watch is only valid for one event, we need to loop to continue watching _, _, ch, err := w.conn.ChildrenW(w.prefix) if err != nil { // If the target service node has not been created if errors.Is(err, zk.ErrNoNode) { // Add watcher for the node exists _, _, ch, err = w.conn.ExistsW(w.prefix) } if err != nil { w.event <- zk.Event{Err: err} continue } } select { case <-ctx.Done(): return case ev := <-ch: w.event <- ev } } } func (w *watcher) Next() ([]*registry.ServiceInstance, error) { // TODO: multiple calls to Next may lead to inconsistent service instance information if w.first.CompareAndSwap(false, true) { return w.getServices() } select { case <-w.ctx.Done(): return nil, w.ctx.Err() case e := <-w.event: if e.State == zk.StateDisconnected { return nil, ErrWatcherStopped } if e.Err != nil { return nil, e.Err } return w.getServices() } } func (w *watcher) Stop() error { w.cancel() return nil } func (w *watcher) getServices() ([]*registry.ServiceInstance, error) { servicesID, _, err := w.conn.Children(w.prefix) if err != nil { return nil, err } items := make([]*registry.ServiceInstance, 0, len(servicesID)) for _, id := range servicesID { servicePath := path.Join(w.prefix, id) b, _, err := w.conn.Get(servicePath) if err != nil { return nil, err } item, err := unmarshal(b) if err != nil { return nil, err } // if the service name of the retrieved instance does not match the watcher's service name, skip it if item.Name != w.serviceName { continue } items = append(items, item) } return items, nil } ================================================ FILE: contrib/transport/mcp/README.md ================================================ # MCP Transport This module implements the MCP server in Kratos based on mcp-go. [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/go-kratos/kratos/contrib/transport/mcp/v2) ## Quick start ```go import( tm "github.com/go-kratos/kratos/contrib/transport/mcp/v2" mcp "github.com/mark3labs/mcp-go/mcp" ) func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { name, ok := request.Params.Arguments["name"].(string) if !ok { return nil, errors.New("name must be a string") } return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil } func Health(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/health/ready" { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } func main() { srv := tm.NewServer("kratos-mcp", "v1.0.0", tm.Address(":8000"), tm.Middleware(Health)) tool := mcp.NewTool("hello_world", mcp.WithDescription("Say hello to someone"), mcp.WithString("name", mcp.Required(), mcp.Description("Name of the person to greet"), ), ) // Add tool handler srv.AddTool(tool, helloHandler) // creates a kratos application app := kratos.New( kratos.Name("kratos-app"), kratos.Server(srv), ) if err := app.Run(); err != nil { panic(err) } } ``` ================================================ FILE: contrib/transport/mcp/go.mod ================================================ module github.com/go-kratos/kratos/contrib/transport/mcp/v2 go 1.23 toolchain go1.24.6 require ( github.com/go-kratos/kratos/v2 v2.9.2 github.com/mark3labs/mcp-go v0.23.0 ) require ( github.com/go-playground/form/v4 v4.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kratos/kratos/v2 => ../../../ ================================================ FILE: contrib/transport/mcp/go.sum ================================================ 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mark3labs/mcp-go v0.23.0 h1:NmtoPx4jf7if7bfAynocpVdpXqX5U8X/18c1gddK/QA= github.com/mark3labs/mcp-go v0.23.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: contrib/transport/mcp/server.go ================================================ package mcp import ( "context" "errors" "fmt" "net/http" "net/url" "github.com/go-kratos/kratos/v2/transport" "github.com/mark3labs/mcp-go/server" ) var ( _ transport.Server = (*Server)(nil) _ transport.Endpointer = (*Server)(nil) _ http.Handler = (*Server)(nil) ) // MiddlewareFunc is a function that takes an http.Handler and returns an http.Handler. type MiddlewareFunc func(http.Handler) http.Handler // ServerOption is an HTTP server option. type ServerOption func(*Server) // Address with server address. func Address(addr string) ServerOption { return func(s *Server) { s.address = addr } } // Endpoint with server address. func Endpoint(endpoint *url.URL) ServerOption { return func(s *Server) { s.endpoint = endpoint } } // Middleware with server middleware. func Middleware(m MiddlewareFunc) ServerOption { return func(s *Server) { s.middleware = m } } // SrvOptions with server options. func SrvOptions(opts ...server.ServerOption) ServerOption { return func(s *Server) { s.srvOpts = append(s.srvOpts, opts...) } } // SSEOptions with server SSE options. func SSEOptions(opts ...server.SSEOption) ServerOption { return func(s *Server) { s.sseOpts = append(s.sseOpts, opts...) } } // Server is a MCP server. type Server struct { *server.MCPServer srv *http.Server sse *server.SSEServer middleware MiddlewareFunc address string endpoint *url.URL srvOpts []server.ServerOption sseOpts []server.SSEOption } // NewServer creates a new MCP server. func NewServer(name, version string, opts ...ServerOption) *Server { srv := &Server{ address: ":8000", middleware: func(next http.Handler) http.Handler { return next }, } for _, o := range opts { o(srv) } srv.MCPServer = server.NewMCPServer(name, version, srv.srvOpts...) srv.srv = &http.Server{Addr: srv.address, Handler: srv.middleware(srv)} srv.sse = server.NewSSEServer(srv.MCPServer, append(srv.sseOpts, server.WithHTTPServer(srv.srv))...) return srv } // ServeHTTP implements the http.Handler interface. func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { s.sse.ServeHTTP(res, req) } // Endpoint return a real address to registry endpoint. // examples: // - http://127.0.0.1:8000 func (s *Server) Endpoint() (*url.URL, error) { if s.endpoint != nil { return s.endpoint, nil } return url.Parse(fmt.Sprintf("http://%s", s.address)) } // Start start the MCP server. func (s *Server) Start(_ context.Context) error { if err := s.srv.ListenAndServe(); err != nil { if !errors.Is(err, http.ErrServerClosed) { return err } } return nil } // Stop stop the MCP server. func (s *Server) Stop(ctx context.Context) error { return s.sse.Shutdown(ctx) } ================================================ FILE: contrib/transport/mcp/server_test.go ================================================ package mcp import ( "context" "testing" "time" ) func TestServer(t *testing.T) { var ( ctx = context.Background() srv = NewServer("test", "v1.0.0", Address(":0")) ) go func() { if err := srv.Start(ctx); err != nil { panic(err) } }() time.Sleep(time.Second) if err := srv.Stop(ctx); err != nil { t.Fatal(err) } } ================================================ FILE: docs/README.md ================================================ ================================================ FILE: docs/design/kratos-v2.md ================================================ # Kratos v2 Kit Design MaoJian Last updated: December 25, 2020 ## Abstract kratos v1 基础库主要专注在各类功能的细节实现,比如 gRPC 的负载均衡,熔断器等一系列微服务需要的功能。 ## Background ## Proposal ## Implementation ================================================ FILE: encoding/README.md ================================================ # encoding ## msgpack ```shell go get -u github.com/go-kratos/kratos/contrib/encoding/msgpack/v2 ``` ================================================ FILE: encoding/encoding.go ================================================ package encoding import ( "strings" ) // Codec defines the interface Transport uses to encode and decode messages. Note // that implementations of this interface must be thread safe; a Codec's // methods can be called from concurrent goroutines. type Codec interface { // Marshal returns the wire format of v. Marshal(v any) ([]byte, error) // Unmarshal parses the wire format into v. Unmarshal(data []byte, v any) error // Name returns the name of the Codec implementation. The returned string // will be used as part of content type in transmission. The result must be // static; the result cannot change between calls. Name() string } var registeredCodecs = make(map[string]Codec) // RegisterCodec registers the provided Codec for use with all Transport clients and // servers. func RegisterCodec(codec Codec) { if codec == nil { panic("cannot register a nil Codec") } if codec.Name() == "" { panic("cannot register Codec with empty string result for Name()") } contentSubtype := strings.ToLower(codec.Name()) registeredCodecs[contentSubtype] = codec } // GetCodec gets a registered Codec by content-subtype, or nil if no Codec is // registered for the content-subtype. // // The content-subtype is expected to be lowercase. func GetCodec(contentSubtype string) Codec { return registeredCodecs[contentSubtype] } ================================================ FILE: encoding/encoding_test.go ================================================ package encoding import ( "encoding/xml" "runtime/debug" "testing" ) type codec struct{} func (c codec) Marshal(_ any) ([]byte, error) { panic("implement me") } func (c codec) Unmarshal(_ []byte, _ any) error { panic("implement me") } func (c codec) Name() string { return "" } // codec2 is a Codec implementation with xml. type codec2 struct{} func (codec2) Marshal(v any) ([]byte, error) { return xml.Marshal(v) } func (codec2) Unmarshal(data []byte, v any) error { return xml.Unmarshal(data, v) } func (codec2) Name() string { return "xml" } func TestRegisterCodec(t *testing.T) { f := func() { RegisterCodec(nil) } funcDidPanic, panicValue, _ := didPanic(f) if !funcDidPanic { t.Fatalf("func should panic\n\tPanic value:\t%#v", panicValue) } if panicValue != "cannot register a nil Codec" { t.Fatalf("panic error got %s want cannot register a nil Codec", panicValue) } f = func() { RegisterCodec(codec{}) } funcDidPanic, panicValue, _ = didPanic(f) if !funcDidPanic { t.Fatalf("func should panic\n\tPanic value:\t%#v", panicValue) } if panicValue != "cannot register Codec with empty string result for Name()" { t.Fatalf("panic error got %s want cannot register Codec with empty string result for Name()", panicValue) } codec := codec2{} RegisterCodec(codec) got := GetCodec("xml") if got != codec { t.Fatalf("RegisterCodec(%v) want %v got %v", codec, codec, got) } } // PanicTestFunc defines a func that should be passed to assert.Panics and assert.NotPanics // methods, and represents a simple func that takes no arguments, and returns nothing. type PanicTestFunc func() // didPanic returns true if the function passed to it panics. Otherwise, it returns false. func didPanic(f PanicTestFunc) (bool, any, string) { didPanic := false var message any var stack string func() { defer func() { if message = recover(); message != nil { didPanic = true stack = string(debug.Stack()) } }() // call the target function f() }() return didPanic, message, stack } ================================================ FILE: encoding/form/form.go ================================================ package form import ( "net/url" "reflect" "github.com/go-playground/form/v4" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/encoding" ) const ( // Name is form codec name Name = "x-www-form-urlencoded" // Null value string nullStr = "null" ) var ( encoder = form.NewEncoder() decoder = form.NewDecoder() ) // This variable can be replaced with -ldflags like below: // go build "-ldflags=-X github.com/go-kratos/kratos/v2/encoding/form.tagName=form" var tagName = "json" func init() { decoder.SetTagName(tagName) encoder.SetTagName(tagName) encoding.RegisterCodec(codec{encoder: encoder, decoder: decoder}) } type codec struct { encoder *form.Encoder decoder *form.Decoder } func (c codec) Marshal(v any) ([]byte, error) { var vs url.Values var err error if m, ok := v.(proto.Message); ok { vs, err = EncodeValues(m) if err != nil { return nil, err } } else { vs, err = c.encoder.Encode(v) if err != nil { return nil, err } } for k, v := range vs { if len(v) == 0 { delete(vs, k) } } return []byte(vs.Encode()), nil } func (c codec) Unmarshal(data []byte, v any) error { vs, err := url.ParseQuery(string(data)) if err != nil { return err } rv := reflect.ValueOf(v) for rv.Kind() == reflect.Ptr { if rv.IsNil() { rv.Set(reflect.New(rv.Type().Elem())) } rv = rv.Elem() } if m, ok := v.(proto.Message); ok { return DecodeValues(m, vs) } if m, ok := rv.Interface().(proto.Message); ok { return DecodeValues(m, vs) } return c.decoder.Decode(v, vs) } func (codec) Name() string { return Name } ================================================ FILE: encoding/form/form_test.go ================================================ package form import ( "encoding/base64" "reflect" "testing" "time" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/fieldmaskpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" "github.com/go-kratos/kratos/v2/encoding" bdtest "github.com/go-kratos/kratos/v2/internal/testdata/binding" "github.com/go-kratos/kratos/v2/internal/testdata/complex" ectest "github.com/go-kratos/kratos/v2/internal/testdata/encoding" ) // This variable can be replaced with -ldflags like below: // go test "-ldflags=-X github.com/go-kratos/kratos/v2/encoding/form.tagNameTest=form" var tagNameTest string func init() { if tagNameTest == "" { tagNameTest = tagName } } func TestFormEncoderAndDecoder(t *testing.T) { t.Cleanup(func() { encoder.SetTagName(tagName) decoder.SetTagName(tagName) }) encoder.SetTagName(tagNameTest) decoder.SetTagName(tagNameTest) type testFormTagName struct { Name string `form:"name_form" json:"name_json"` } v, err := encoder.Encode(&testFormTagName{ Name: "test tag name", }) if err != nil { t.Fatal(err) } jsonName := v.Get("name_json") formName := v.Get("name_form") switch tagNameTest { case "json": if jsonName != "test tag name" { t.Errorf("got: %s", jsonName) } if formName != "" { t.Errorf("want: empty, got: %s", formName) } case "form": if formName != "test tag name" { t.Errorf("got: %s", formName) } if jsonName != "" { t.Errorf("want: empty, got: %s", jsonName) } default: t.Fatalf("unknown tag name: %s", tagNameTest) } var tn *testFormTagName err = decoder.Decode(&tn, v) if err != nil { t.Fatal(err) } if tn == nil { t.Fatal("nil tag name") } if tn.Name != "test tag name" { t.Errorf("got %s", tn.Name) } } type LoginRequest struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` } func TestFormCodecMarshal(t *testing.T) { req := &LoginRequest{ Username: "kratos", Password: "kratos_pwd", } content, err := encoding.GetCodec(Name).Marshal(req) if err != nil { t.Fatal(err) } if !reflect.DeepEqual([]byte("password=kratos_pwd&username=kratos"), content) { t.Errorf("expect %s, got %s", "password=kratos_pwd&username=kratos", content) } req = &LoginRequest{ Username: "kratos", Password: "", } content, err = encoding.GetCodec(Name).Marshal(req) if err != nil { t.Fatal(err) } if !reflect.DeepEqual([]byte("username=kratos"), content) { t.Errorf("expect %s, got %s", "username=kratos", content) } m := struct { ID int32 `json:"id"` Name string `json:"name"` }{ ID: 1, Name: "kratos", } content, err = encoding.GetCodec(Name).Marshal(m) if err != nil { t.Fatal(err) } if !reflect.DeepEqual([]byte("id=1&name=kratos"), content) { t.Errorf("expect %s, got %s", "id=1&name=kratos", content) } } func TestFormCodecUnmarshal(t *testing.T) { req := &LoginRequest{ Username: "kratos", Password: "kratos_pwd", } content, err := encoding.GetCodec(Name).Marshal(req) if err != nil { t.Fatal(err) } bindReq := new(LoginRequest) err = encoding.GetCodec(Name).Unmarshal(content, bindReq) if err != nil { t.Fatal(err) } if !reflect.DeepEqual("kratos", bindReq.Username) { t.Errorf("expect %v, got %v", "kratos", bindReq.Username) } if !reflect.DeepEqual("kratos_pwd", bindReq.Password) { t.Errorf("expect %v, got %v", "kratos_pwd", bindReq.Password) } } //nolint:staticcheck func TestProtoEncodeDecode(t *testing.T) { in := &complex.Complex{ Id: 2233, NoOne: "2233", Simple: &complex.Simple{Component: "5566"}, Strings: []string{"3344", "5566"}, B: true, Sex: complex.Sex_woman, Age: 18, A: 19, Count: 3, Price: 11.23, D: 22.22, Byte: []byte("123"), Map: map[string]string{"kratos": "https://go-kratos.dev/", "kratos_start": "https://go-kratos.dev/en/docs/getting-started/start/"}, Timestamp: timestamppb.New(time.Date(1970, 1, 1, 0, 0, 20, 2, time.Local)), Duration: &durationpb.Duration{Seconds: 120, Nanos: 22}, Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}}, Double: &wrapperspb.DoubleValue{Value: 12.33}, Float: &wrapperspb.FloatValue{Value: 12.34}, Int64: &wrapperspb.Int64Value{Value: 64}, Int32: &wrapperspb.Int32Value{Value: 32}, Uint64: &wrapperspb.UInt64Value{Value: 64}, Uint32: &wrapperspb.UInt32Value{Value: 32}, Bool: &wrapperspb.BoolValue{Value: false}, String_: &wrapperspb.StringValue{Value: "go-kratos"}, Bytes: &wrapperspb.BytesValue{Value: []byte("123")}, } content, err := encoding.GetCodec(Name).Marshal(in) if err != nil { t.Fatal(err) } if "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration="+ "2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&"+ "map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&map%5Bkratos_start%5D=https%3A%2F%2Fgo-kratos.dev%2Fen%2Fdocs%2Fgetting-started%2Fstart%2F&"+ "numberOne=2233&price=11.23&sex=woman&string=go-kratos&strings=3344&strings=5566"+ "×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" != string(content) { t.Errorf("rawpath is not equal to %s", content) } in2 := &complex.Complex{} err = encoding.GetCodec(Name).Unmarshal(content, in2) if err != nil { t.Fatal(err) } if int64(2233) != in2.Id { t.Errorf("expect %v, got %v", int64(2233), in2.Id) } if "2233" != in2.NoOne { t.Errorf("expect %v, got %v", "2233", in2.NoOne) } if in2.Simple == nil { t.Errorf("expect %v, got %v", nil, in2.Simple) } if "5566" != in2.Simple.Component { t.Errorf("expect %v, got %v", "5566", in2.Simple.Component) } if in2.Strings == nil { t.Errorf("expect %v, got %v", nil, in2.Strings) } if len(in2.Strings) != 2 { t.Errorf("expect %v, got %v", 2, len(in2.Strings)) } if "3344" != in2.Strings[0] { t.Errorf("expect %v, got %v", "3344", in2.Strings[0]) } if "5566" != in2.Strings[1] { t.Errorf("expect %v, got %v", "5566", in2.Strings[1]) } if l := len(in2.GetMap()); l != 2 { t.Fatalf("in2.Map length want: %d, got: %d", 2, l) } for key, val := range in.GetMap() { if in2Val := in2.GetMap()[key]; in2Val != val { t.Errorf("%s want: %q, got: %q", "map["+key+"]", val, in2Val) } } } //nolint:staticcheck func TestDecodeStructPb(t *testing.T) { req := new(ectest.StructPb) query := `data={"name":"kratos"}&data_list={"name1": "kratos"}&data_list={"name2": "go-kratos"}` if err := encoding.GetCodec(Name).Unmarshal([]byte(query), req); err != nil { t.Fatal(err) } if "kratos" != req.Data.GetFields()["name"].GetStringValue() { t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name"].GetStringValue()) } if len(req.DataList) != 2 { t.Fatalf("except %v, got %v", 2, len(req.DataList)) } if "kratos" != req.DataList[0].GetFields()["name1"].GetStringValue() { t.Errorf("except %v, got %v", "kratos", req.Data.GetFields()["name1"].GetStringValue()) } if "go-kratos" != req.DataList[1].GetFields()["name2"].GetStringValue() { t.Errorf("except %v, got %v", "go-kratos", req.Data.GetFields()["name2"].GetStringValue()) } } func TestDecodeBytesValuePb(t *testing.T) { url := "https://example.com/xx/?a=1&b=2&c=3" val := base64.URLEncoding.EncodeToString([]byte(url)) content := "bytes=" + val in2 := &complex.Complex{} if err := encoding.GetCodec(Name).Unmarshal([]byte(content), in2); err != nil { t.Fatal(err) } if url != string(in2.Bytes.Value) { t.Errorf("except %s, got %s", val, in2.Bytes.Value) } } func TestEncodeFieldMask(t *testing.T) { req := &bdtest.HelloRequest{ UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"foo", "bar"}}, } if v := EncodeFieldMask(req.ProtoReflect()); v != "updateMask=foo,bar" { t.Errorf("got %s", v) } } func TestOptional(t *testing.T) { v := int32(100) req := &bdtest.HelloRequest{ Name: "foo", Sub: &bdtest.Sub{Name: "bar"}, OptInt32: &v, } query, _ := EncodeValues(req) if query.Encode() != "name=foo&optInt32=100&sub.naming=bar" { t.Fatalf("got %s", query.Encode()) } } func TestWithUnsupportedType(t *testing.T) { in := &complex.Complex{ Id: 2233, Simples: []*complex.Simple{{Component: "3344"}, {Component: "5566"}}, } query, _ := EncodeValues(in) if query.Encode() != "id=2233" { t.Fatalf("got %s", query.Encode()) } } ================================================ FILE: encoding/form/proto_decode.go ================================================ package form import ( "encoding/base64" "errors" "fmt" "net/url" "strconv" "strings" "time" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/fieldmaskpb" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) const fieldSeparator = "." var errInvalidFormatMapKey = errors.New("invalid formatting for map key") // DecodeValues decode url value into proto message. func DecodeValues(msg proto.Message, values url.Values) error { for key, values := range values { if err := populateFieldValues(msg.ProtoReflect(), strings.Split(key, "."), values); err != nil { return err } } return nil } func populateFieldValues(v protoreflect.Message, fieldPath []string, values []string) error { if len(fieldPath) < 1 { return errors.New("no field path") } if len(values) < 1 { return errors.New("no value provided") } var fd protoreflect.FieldDescriptor for i, fieldName := range fieldPath { if fd = getFieldDescriptor(v, fieldName); fd == nil { // ignore unexpected field. return nil } if fd.IsMap() && len(fieldPath) == 2 { return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values) } if i == len(fieldPath)-1 { break } if fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated { if fd.IsMap() && len(fieldPath) > 1 { // post subfield return populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values) } return fmt.Errorf("invalid path: %q is not a message", fieldName) } v = v.Mutable(fd).Message() } if of := fd.ContainingOneof(); of != nil { if f := v.WhichOneof(of); f != nil { return fmt.Errorf("field already set for oneof %q", of.FullName().Name()) } } switch { case fd.IsList(): return populateRepeatedField(fd, v.Mutable(fd).List(), values) case fd.IsMap(): return populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values) } if len(values) > 1 { return fmt.Errorf("too many values for field %q: %s", fd.FullName().Name(), strings.Join(values, ", ")) } return populateField(fd, v, values[0]) } func getFieldDescriptor(v protoreflect.Message, fieldName string) protoreflect.FieldDescriptor { var ( fields = v.Descriptor().Fields() fd = getDescriptorByFieldAndName(fields, fieldName) ) if fd == nil { switch { case v.Descriptor().FullName() == structMessageFullname: fd = fields.ByNumber(structFieldsFieldNumber) case len(fieldName) > 2 && strings.HasSuffix(fieldName, "[]"): fd = getDescriptorByFieldAndName(fields, strings.TrimSuffix(fieldName, "[]")) default: // If the type is map, you get the string "map[kratos]", where "map" is a field of proto and "kratos" is a key of map // Use symbol . for separating fields/structs. (eg. structfield.field) // ref: https://github.com/go-playground/form field, _, err := parseURLQueryMapKey(fieldName) if err != nil { break } fd = getDescriptorByFieldAndName(fields, field) } } return fd } func getDescriptorByFieldAndName(fields protoreflect.FieldDescriptors, fieldName string) protoreflect.FieldDescriptor { var fd protoreflect.FieldDescriptor if fd = fields.ByName(protoreflect.Name(fieldName)); fd == nil { fd = fields.ByJSONName(fieldName) } return fd } func populateField(fd protoreflect.FieldDescriptor, v protoreflect.Message, value string) error { if value == "" { return nil } val, err := parseField(fd, value) if err != nil { return fmt.Errorf("parsing field %q: %w", fd.FullName().Name(), err) } v.Set(fd, val) return nil } func populateRepeatedField(fd protoreflect.FieldDescriptor, list protoreflect.List, values []string) error { for _, value := range values { v, err := parseField(fd, value) if err != nil { return fmt.Errorf("parsing list %q: %w", fd.FullName().Name(), err) } list.Append(v) } return nil } func populateMapField(fd protoreflect.FieldDescriptor, mp protoreflect.Map, fieldPath []string, values []string) error { _, keyName, err := parseURLQueryMapKey(strings.Join(fieldPath, fieldSeparator)) if err != nil { return err } key, err := parseField(fd.MapKey(), keyName) if err != nil { return fmt.Errorf("parsing map key %q: %w", fd.FullName().Name(), err) } value, err := parseField(fd.MapValue(), values[len(values)-1]) if err != nil { return fmt.Errorf("parsing map value %q: %w", fd.FullName().Name(), err) } mp.Set(key.MapKey(), value) return nil } func parseField(fd protoreflect.FieldDescriptor, value string) (protoreflect.Value, error) { switch fd.Kind() { case protoreflect.BoolKind: v, err := strconv.ParseBool(value) if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfBool(v), nil case protoreflect.EnumKind: enum, err := protoregistry.GlobalTypes.FindEnumByName(fd.Enum().FullName()) switch { case errors.Is(err, protoregistry.NotFound): return protoreflect.Value{}, fmt.Errorf("enum %q is not registered", fd.Enum().FullName()) case err != nil: return protoreflect.Value{}, fmt.Errorf("failed to look up enum: %w", err) } v := enum.Descriptor().Values().ByName(protoreflect.Name(value)) if v == nil { i, err := strconv.ParseInt(value, 10, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value) } v = enum.Descriptor().Values().ByNumber(protoreflect.EnumNumber(i)) if v == nil { return protoreflect.Value{}, fmt.Errorf("%q is not a valid value", value) } } return protoreflect.ValueOfEnum(v.Number()), nil case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: v, err := strconv.ParseInt(value, 10, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfInt32(int32(v)), nil case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: v, err := strconv.ParseInt(value, 10, 64) //nolint:mnd if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfInt64(v), nil case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: v, err := strconv.ParseUint(value, 10, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfUint32(uint32(v)), nil case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: v, err := strconv.ParseUint(value, 10, 64) //nolint:mnd if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfUint64(v), nil case protoreflect.FloatKind: v, err := strconv.ParseFloat(value, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfFloat32(float32(v)), nil case protoreflect.DoubleKind: v, err := strconv.ParseFloat(value, 64) //nolint:mnd if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfFloat64(v), nil case protoreflect.StringKind: return protoreflect.ValueOfString(value), nil case protoreflect.BytesKind: v, err := base64.StdEncoding.DecodeString(value) if err != nil { return protoreflect.Value{}, err } return protoreflect.ValueOfBytes(v), nil case protoreflect.MessageKind, protoreflect.GroupKind: return parseMessage(fd.Message(), value) default: panic(fmt.Sprintf("unknown field kind: %v", fd.Kind())) } } func parseMessage(md protoreflect.MessageDescriptor, value string) (protoreflect.Value, error) { var msg proto.Message switch md.FullName() { case "google.protobuf.Timestamp": if value == nullStr { break } t, err := time.ParseInLocation(time.RFC3339Nano, value, time.Local) if err != nil { return protoreflect.Value{}, err } msg = timestamppb.New(t) case "google.protobuf.Duration": if value == nullStr { break } d, err := time.ParseDuration(value) if err != nil { return protoreflect.Value{}, err } msg = durationpb.New(d) case "google.protobuf.DoubleValue": v, err := strconv.ParseFloat(value, 64) //nolint:mnd if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.Double(v) case "google.protobuf.FloatValue": v, err := strconv.ParseFloat(value, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.Float(float32(v)) case "google.protobuf.Int64Value": v, err := strconv.ParseInt(value, 10, 64) //nolint:mnd if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.Int64(v) case "google.protobuf.Int32Value": v, err := strconv.ParseInt(value, 10, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.Int32(int32(v)) case "google.protobuf.UInt64Value": v, err := strconv.ParseUint(value, 10, 64) //nolint:mnd if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.UInt64(v) case "google.protobuf.UInt32Value": v, err := strconv.ParseUint(value, 10, 32) //nolint:mnd if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.UInt32(uint32(v)) case "google.protobuf.BoolValue": v, err := strconv.ParseBool(value) if err != nil { return protoreflect.Value{}, err } msg = wrapperspb.Bool(v) case "google.protobuf.StringValue": msg = wrapperspb.String(value) case "google.protobuf.BytesValue": v, err := base64.StdEncoding.DecodeString(value) if err != nil { if v, err = base64.URLEncoding.DecodeString(value); err != nil { return protoreflect.Value{}, err } } msg = wrapperspb.Bytes(v) case "google.protobuf.FieldMask": fm := &fieldmaskpb.FieldMask{} for _, fv := range strings.Split(value, ",") { fm.Paths = append(fm.Paths, jsonSnakeCase(fv)) } msg = fm case "google.protobuf.Value": fm, err := structpb.NewValue(value) if err != nil { return protoreflect.Value{}, err } msg = fm case "google.protobuf.Struct": var v structpb.Struct if err := protojson.Unmarshal([]byte(value), &v); err != nil { return protoreflect.Value{}, err } msg = &v default: return protoreflect.Value{}, fmt.Errorf("unsupported message type: %q", string(md.FullName())) } return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil } // jsonSnakeCase converts a camelCase identifier to a snake_case identifier, // according to the protobuf JSON specification. // references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864 func jsonSnakeCase(s string) string { var builder strings.Builder builder.Grow(len(s)) for i := 0; i < len(s); i++ { // proto identifiers are always ASCII c := s[i] if isASCIIUpper(c) { builder.WriteByte('_') c += 'a' - 'A' // convert to lowercase } builder.WriteByte(c) } return builder.String() } func isASCIIUpper(c byte) bool { return 'A' <= c && c <= 'Z' } // parseURLQueryMapKey parse the url.Values the field name and key name of the value map type key // for example: convert "map[key]" to "map" and "key" func parseURLQueryMapKey(key string) (string, string, error) { startIndex := strings.IndexByte(key, '[') endIndex := strings.IndexByte(key, ']') if startIndex < 0 { fsCount := strings.Count(key, fieldSeparator) if fsCount != 1 { return "", "", errInvalidFormatMapKey } m, k, _ := strings.Cut(key, fieldSeparator) if m == "" { return "", "", errInvalidFormatMapKey } return m, k, nil } if startIndex <= 0 || startIndex >= endIndex || len(key) != endIndex+1 { return "", "", errInvalidFormatMapKey } return key[:startIndex], key[startIndex+1 : endIndex], nil } ================================================ FILE: encoding/form/proto_decode_test.go ================================================ package form import ( "fmt" "net/url" "reflect" "strconv" "testing" "google.golang.org/protobuf/reflect/protoreflect" "github.com/go-kratos/kratos/v2/internal/testdata/complex" ) func TestDecodeValues(t *testing.T) { form, err := url.ParseQuery("a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=" + "2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&numberOne=2233&price=11.23&sex=woman&strings=3344&" + "strings=5566&string=go-kratos×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566") if err != nil { t.Fatal(err) } comp := &complex.Complex{} err = DecodeValues(comp, form) if err != nil { t.Fatal(err) } if comp.Id != int64(2233) { t.Errorf("want %v, got %v", int64(2233), comp.Id) } if comp.NoOne != "2233" { t.Errorf("want %v, got %v", "2233", comp.NoOne) } if comp.Simple == nil { t.Fatalf("want %v, got %v", nil, comp.Simple) } if comp.Simple.Component != "5566" { t.Errorf("want %v, got %v", "5566", comp.Simple.Component) } if len(comp.Strings) != 2 { t.Fatalf("want %v, got %v", 2, len(comp.Strings)) } if comp.Strings[0] != "3344" { t.Errorf("want %v, got %v", "3344", comp.Strings[0]) } if comp.Strings[1] != "5566" { t.Errorf("want %v, got %v", "5566", comp.Strings[1]) } } func TestGetFieldDescriptor(t *testing.T) { comp := &complex.Complex{} field := getFieldDescriptor(comp.ProtoReflect(), "id") if field.Kind() != protoreflect.Int64Kind { t.Errorf("want: %d, got: %d", protoreflect.Int64Kind, field.Kind()) } field = getFieldDescriptor(comp.ProtoReflect(), "strings") if field.Kind() != protoreflect.StringKind { t.Errorf("want: %d, got: %d", protoreflect.StringKind, field.Kind()) } } func TestPopulateRepeatedField(t *testing.T) { query, err := url.ParseQuery("strings=3344&strings=5566") if err != nil { t.Fatal(err) } comp := &complex.Complex{} field := getFieldDescriptor(comp.ProtoReflect(), "strings") err = populateRepeatedField(field, comp.ProtoReflect().Mutable(field).List(), query["strings"]) if err != nil { t.Fatal(err) } if !reflect.DeepEqual([]string{"3344", "5566"}, comp.GetStrings()) { t.Errorf("want: %v, got: %v", []string{"3344", "5566"}, comp.GetStrings()) } } func TestPopulateMapField(t *testing.T) { query, err := url.ParseQuery("map%5Bkratos%5D=https://go-kratos.dev/") if err != nil { t.Fatal(err) } comp := &complex.Complex{} field := getFieldDescriptor(comp.ProtoReflect(), "map") // Fill the comp map field with the url query values err = populateMapField(field, comp.ProtoReflect().Mutable(field).Map(), []string{"map[kratos]"}, query["map[kratos]"]) if err != nil { t.Fatal(err) } // Get the comp map field value if query["map[kratos]"][0] != comp.Map["kratos"] { t.Errorf("want: %s, got: %s", query["map[kratos]"], comp.Map["kratos"]) } } func TestPopulateMapSepField(t *testing.T) { query, err := url.ParseQuery("map.name=kratos") if err != nil { t.Fatal(err) } comp := &complex.Complex{} field := getFieldDescriptor(comp.ProtoReflect(), "map") // Fill the comp map field with the url query values err = populateMapField(field, comp.ProtoReflect().Mutable(field).Map(), []string{"map.name"}, query["map.name"]) if err != nil { t.Fatal(err) } // Get the comp map field value if query["map.name"][0] != comp.Map["name"] { t.Errorf("want: %s, got: %s", query, comp.Map) } } func TestParseField(t *testing.T) { tests := []struct { name string fieldName string protoReflectKind protoreflect.Kind value string targetProtoReflectValue protoreflect.Value targetErr error }{ { name: "BoolKind", fieldName: "b", protoReflectKind: protoreflect.BoolKind, value: "true", targetProtoReflectValue: protoreflect.ValueOfBool(true), targetErr: nil, }, { name: "BoolKind", fieldName: "b", protoReflectKind: protoreflect.BoolKind, value: "a", targetProtoReflectValue: protoreflect.Value{}, targetErr: &strconv.NumError{Func: "ParseBool", Num: "a", Err: strconv.ErrSyntax}, }, { name: "EnumKind", fieldName: "sex", protoReflectKind: protoreflect.EnumKind, value: "1", targetProtoReflectValue: protoreflect.ValueOfEnum(1), targetErr: nil, }, { name: "EnumKind", fieldName: "sex", protoReflectKind: protoreflect.EnumKind, value: "2", targetProtoReflectValue: protoreflect.Value{}, targetErr: fmt.Errorf("%q is not a valid value", "2"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { comp := &complex.Complex{} field := getFieldDescriptor(comp.ProtoReflect(), test.fieldName) if test.protoReflectKind != field.Kind() { t.Fatalf("want: %d, got: %d", test.protoReflectKind, field.Kind()) } val, err := parseField(field, test.value) if !reflect.DeepEqual(test.targetErr, err) { t.Fatalf("want: %s, got: %s", test.targetErr, err) } if !reflect.DeepEqual(test.targetProtoReflectValue, val) { t.Errorf("want: %s, got: %s", test.targetProtoReflectValue, val) } }) } } func TestJsonSnakeCase(t *testing.T) { tests := []struct { camelCase string snakeCase string }{ { "userId", "user_id", }, { "user", "user", }, { "userIdAndUsername", "user_id_and_username", }, { "", "", }, } for _, test := range tests { t.Run(test.camelCase, func(t *testing.T) { snake := jsonSnakeCase(test.camelCase) if snake != test.snakeCase { t.Errorf("want: %s, got: %s", test.snakeCase, snake) } }) } } func TestIsASCIIUpper(t *testing.T) { tests := []struct { b byte upper bool }{ { 'A', true, }, { 'a', false, }, { ',', false, }, { '1', false, }, { ' ', false, }, } for _, test := range tests { t.Run(string(test.b), func(t *testing.T) { upper := isASCIIUpper(test.b) if test.upper != upper { t.Errorf("'%s' is not ascii upper", string(test.b)) } }) } } func TestParseURLQueryMapKey(t *testing.T) { tests := []struct { fieldName string field string fieldKey string err error }{ { fieldName: "map[kratos]", field: "map", fieldKey: "kratos", err: nil, }, { fieldName: "map[]", field: "map", fieldKey: "", err: nil, }, { fieldName: "", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "[[]", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "map[kratos]=", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "[kratos]", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "map", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "map[", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "]kratos[", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "[kratos", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "kratos]", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "map.kratos", field: "map", fieldKey: "kratos", err: nil, }, { fieldName: "map.", field: "map", fieldKey: "", err: nil, }, { fieldName: ".kratos", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, { fieldName: "map.kratos.v2", field: "", fieldKey: "", err: errInvalidFormatMapKey, }, } for _, test := range tests { t.Run(test.fieldName, func(t *testing.T) { fieldName, fieldKey, err := parseURLQueryMapKey(test.fieldName) if test.err != err { t.Fatalf("want: %s, got: %s", test.err, err) } if test.field != fieldName { t.Errorf("want: %s, got: %s", test.field, fieldName) } if test.fieldKey != fieldKey { t.Errorf("want: %s, got: %s", test.fieldKey, fieldKey) } }) } } func BenchmarkParseURLQueryMapKey(b *testing.B) { testCases := []struct { testName string fieldName string wantedField string wantedFieldKey string wantedErr error }{ { testName: "with bracket", fieldName: "kratos[version]", wantedField: "kratos", wantedFieldKey: "version", wantedErr: nil, }, { testName: "with point", fieldName: "kratos.version", wantedField: "kratos", wantedFieldKey: "version", wantedErr: nil, }, } for _, testCase := range testCases { b.Run(testCase.testName, func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { field, fieldKey, err := parseURLQueryMapKey(testCase.fieldName) if testCase.wantedErr != err { b.Fatalf("want: %s, got: %s", testCase.wantedErr, err) } if testCase.wantedField != field { b.Errorf("want: %s, got: %s", testCase.wantedField, field) } if testCase.wantedFieldKey != fieldKey { b.Errorf("want: %s, got: %s", testCase.wantedFieldKey, fieldKey) } } }) } } ================================================ FILE: encoding/form/proto_encode.go ================================================ package form import ( "encoding/base64" "fmt" "net/url" "reflect" "strconv" "strings" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/fieldmaskpb" ) // EncodeValues encode a message into url values. func EncodeValues(msg any) (url.Values, error) { if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) { return url.Values{}, nil } if v, ok := msg.(proto.Message); ok { u := make(url.Values) err := encodeByField(u, "", v.ProtoReflect()) return u, err } return encoder.Encode(msg) } func encodeByField(u url.Values, path string, m protoreflect.Message) (finalErr error) { m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { var ( key string newPath string ) if fd.HasJSONName() { key = fd.JSONName() } else { key = fd.TextName() } if path == "" { newPath = key } else { newPath = path + "." + key } if of := fd.ContainingOneof(); of != nil { if f := m.WhichOneof(of); f != nil && f != fd { return true } } switch { case fd.IsList(): if v.List().Len() > 0 { list, err := encodeRepeatedField(fd, v.List()) if err != nil { finalErr = err } for _, item := range list { u.Add(newPath, item) } } case fd.IsMap(): if v.Map().Len() > 0 { m, err := encodeMapField(fd, v.Map()) if err != nil { finalErr = err } for k, value := range m { u.Set(newPath+"["+k+"]", value) } } case (fd.Kind() == protoreflect.MessageKind) || (fd.Kind() == protoreflect.GroupKind): value, err := encodeMessage(fd.Message(), v) if err == nil { u.Set(newPath, value) return true } if err = encodeByField(u, newPath, v.Message()); err != nil { finalErr = err } default: value, err := EncodeField(fd, v) if err != nil { finalErr = err } u.Set(newPath, value) } return true }) return } func encodeRepeatedField(fieldDescriptor protoreflect.FieldDescriptor, list protoreflect.List) ([]string, error) { var values []string for i := 0; i < list.Len(); i++ { value, err := EncodeField(fieldDescriptor, list.Get(i)) if err != nil { return nil, err } values = append(values, value) } return values, nil } func encodeMapField(fieldDescriptor protoreflect.FieldDescriptor, mp protoreflect.Map) (map[string]string, error) { m := make(map[string]string) mp.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool { key, err := EncodeField(fieldDescriptor.MapKey(), k.Value()) if err != nil { return false } value, err := EncodeField(fieldDescriptor.MapValue(), v) if err != nil { return false } m[key] = value return true }) return m, nil } // EncodeField encode proto message filed func EncodeField(fieldDescriptor protoreflect.FieldDescriptor, value protoreflect.Value) (string, error) { switch fieldDescriptor.Kind() { case protoreflect.BoolKind: return strconv.FormatBool(value.Bool()), nil case protoreflect.EnumKind: if fieldDescriptor.Enum().FullName() == "google.protobuf.NullValue" { return nullStr, nil } desc := fieldDescriptor.Enum().Values().ByNumber(value.Enum()) return string(desc.Name()), nil case protoreflect.BytesKind: return base64.URLEncoding.EncodeToString(value.Bytes()), nil case protoreflect.MessageKind, protoreflect.GroupKind: return encodeMessage(fieldDescriptor.Message(), value) default: return value.String(), nil } } // encodeMessage marshals the fields in the given protoreflect.Message. // If the typeURL is non-empty, then a synthetic "@type" field is injected // containing the URL as the value. func encodeMessage(msgDescriptor protoreflect.MessageDescriptor, value protoreflect.Value) (string, error) { switch msgDescriptor.FullName() { case timestampMessageFullname: return marshalTimestamp(value.Message()) case durationMessageFullname: return marshalDuration(value.Message()) case bytesMessageFullname: return marshalBytes(value.Message()) case "google.protobuf.DoubleValue", "google.protobuf.FloatValue", "google.protobuf.Int64Value", "google.protobuf.Int32Value", "google.protobuf.UInt64Value", "google.protobuf.UInt32Value", "google.protobuf.BoolValue", "google.protobuf.StringValue": fd := msgDescriptor.Fields() v := value.Message().Get(fd.ByName("value")) return fmt.Sprint(v.Interface()), nil case fieldMaskFullName: m, ok := value.Message().Interface().(*fieldmaskpb.FieldMask) if !ok || m == nil { return "", nil } for i, v := range m.Paths { m.Paths[i] = jsonCamelCase(v) } return strings.Join(m.Paths, ","), nil default: return "", fmt.Errorf("unsupported message type: %q", string(msgDescriptor.FullName())) } } // EncodeFieldMask return field mask name=paths func EncodeFieldMask(m protoreflect.Message) (query string) { m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { if fd.Kind() == protoreflect.MessageKind { if msg := fd.Message(); msg.FullName() == fieldMaskFullName { value, err := encodeMessage(msg, v) if err != nil { return false } if fd.HasJSONName() { query = fd.JSONName() + "=" + value } else { query = fd.TextName() + "=" + value } return false } } return true }) return } // jsonCamelCase converts a snake_case identifier to a camelCase identifier, // according to the protobuf JSON specification. // references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L842 func jsonCamelCase(s string) string { var builder strings.Builder builder.Grow(len(s)) wasUnderscore := false for i := 0; i < len(s); i++ { // proto identifiers are always ASCIIS c := s[i] if c != '_' { if wasUnderscore && isASCIILower(c) { c -= 'a' - 'A' // convert to uppercase } builder.WriteByte(c) } wasUnderscore = c == '_' } return builder.String() } func isASCIILower(c byte) bool { return 'a' <= c && c <= 'z' } ================================================ FILE: encoding/form/proto_encode_test.go ================================================ package form import ( "testing" "time" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/fieldmaskpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" "github.com/go-kratos/kratos/v2/internal/testdata/complex" ) func TestEncodeValues(t *testing.T) { in := &complex.Complex{ Id: 2233, NoOne: "2233", Simple: &complex.Simple{Component: "5566"}, Strings: []string{"3344", "5566"}, B: true, Sex: complex.Sex_woman, Age: 18, A: 19, Count: 3, Price: 11.23, D: 22.22, Byte: []byte("123"), Map: map[string]string{"kratos": "https://go-kratos.dev/", "kratos_start": "https://go-kratos.dev/docs/getting-started/start/"}, MapInt64Key: map[int64]string{1: "kratos", 2: "go-zero"}, Timestamp: timestamppb.New(time.Date(1970, 1, 1, 0, 0, 20, 2, time.Local)), Duration: &durationpb.Duration{Seconds: 120, Nanos: 22}, Field: &fieldmaskpb.FieldMask{Paths: []string{"1", "2"}}, Double: &wrapperspb.DoubleValue{Value: 12.33}, Float: &wrapperspb.FloatValue{Value: 12.34}, Int64: &wrapperspb.Int64Value{Value: 64}, Int32: &wrapperspb.Int32Value{Value: 32}, Uint64: &wrapperspb.UInt64Value{Value: 64}, Uint32: &wrapperspb.UInt32Value{Value: 32}, Bool: &wrapperspb.BoolValue{Value: false}, String_: &wrapperspb.StringValue{Value: "go-kratos"}, Bytes: &wrapperspb.BytesValue{Value: []byte("123")}, } query, err := EncodeValues(in) if err != nil { t.Fatal(err) } want := "a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&map%5Bkratos%5D=https%3A%2F%2Fgo-kratos.dev%2F&map%5Bkratos_start%5D=https%3A%2F%2Fgo-kratos.dev%2Fdocs%2Fgetting-started%2Fstart%2F&map_int64_key%5B1%5D=kratos&map_int64_key%5B2%5D=go-zero&numberOne=2233&price=11.23&sex=woman&string=go-kratos&strings=3344&strings=5566×tamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566" // nolint:lll if got := query.Encode(); want != got { t.Errorf("\nwant: %s, \ngot: %s", want, got) } } func TestJsonCamelCase(t *testing.T) { tests := []struct { camelCase string snakeCase string }{ { "userId", "user_id", }, { "user", "user", }, { "userIdAndUsername", "user_id_and_username", }, { "", "", }, } for _, test := range tests { t.Run(test.snakeCase, func(t *testing.T) { camel := jsonCamelCase(test.snakeCase) if camel != test.camelCase { t.Errorf("want: %s, got: %s", test.camelCase, camel) } }) } } func TestIsASCIILower(t *testing.T) { tests := []struct { b byte lower bool }{ { 'A', false, }, { 'a', true, }, { ',', false, }, { '1', false, }, { ' ', false, }, } for _, test := range tests { t.Run(string(test.b), func(t *testing.T) { lower := isASCIILower(test.b) if test.lower != lower { t.Errorf("'%s' is not ascii lower", string(test.b)) } }) } } ================================================ FILE: encoding/form/well_known_types.go ================================================ package form import ( "encoding/base64" "fmt" "math" "strings" "time" "google.golang.org/protobuf/reflect/protoreflect" ) const ( // timestamp timestampMessageFullname protoreflect.FullName = "google.protobuf.Timestamp" maxTimestampSeconds = 253402300799 minTimestampSeconds = -6213559680013 timestampSecondsFieldNumber protoreflect.FieldNumber = 1 timestampNanosFieldNumber protoreflect.FieldNumber = 2 // duration durationMessageFullname protoreflect.FullName = "google.protobuf.Duration" secondsInNanos = 999999999 durationSecondsFieldNumber protoreflect.FieldNumber = 1 durationNanosFieldNumber protoreflect.FieldNumber = 2 // bytes bytesMessageFullname protoreflect.FullName = "google.protobuf.BytesValue" bytesValueFieldNumber protoreflect.FieldNumber = 1 // google.protobuf.Struct. structMessageFullname protoreflect.FullName = "google.protobuf.Struct" structFieldsFieldNumber protoreflect.FieldNumber = 1 fieldMaskFullName protoreflect.FullName = "google.protobuf.FieldMask" ) func marshalTimestamp(m protoreflect.Message) (string, error) { fds := m.Descriptor().Fields() fdSeconds := fds.ByNumber(timestampSecondsFieldNumber) fdNanos := fds.ByNumber(timestampNanosFieldNumber) secsVal := m.Get(fdSeconds) nanosVal := m.Get(fdNanos) secs := secsVal.Int() nanos := nanosVal.Int() if secs < minTimestampSeconds || secs > maxTimestampSeconds { return "", fmt.Errorf("%s: seconds out of range %v", timestampMessageFullname, secs) } if nanos < 0 || nanos > secondsInNanos { return "", fmt.Errorf("%s: nanos out of range %v", timestampMessageFullname, nanos) } // Uses RFC 3339, where generated output will be Z-normalized and uses 0, 3, // 6 or 9 fractional digits. t := time.Unix(secs, nanos).Local() x := t.Format("2006-01-02T15:04:05.000000000") x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, "000") x = strings.TrimSuffix(x, ".000") return x + "Z", nil } func marshalDuration(m protoreflect.Message) (string, error) { fds := m.Descriptor().Fields() fdSeconds := fds.ByNumber(durationSecondsFieldNumber) fdNanos := fds.ByNumber(durationNanosFieldNumber) secsVal := m.Get(fdSeconds) nanosVal := m.Get(fdNanos) secs := secsVal.Int() nanos := nanosVal.Int() d := time.Duration(secs) * time.Second overflow := d/time.Second != time.Duration(secs) d += time.Duration(nanos) * time.Nanosecond overflow = overflow || (secs < 0 && nanos < 0 && d > 0) overflow = overflow || (secs > 0 && nanos > 0 && d < 0) if overflow { switch { case secs < 0: return time.Duration(math.MinInt64).String(), nil case secs > 0: return time.Duration(math.MaxInt64).String(), nil } } return d.String(), nil } func marshalBytes(m protoreflect.Message) (string, error) { fds := m.Descriptor().Fields() fdBytes := fds.ByNumber(bytesValueFieldNumber) bytesVal := m.Get(fdBytes) val := bytesVal.Bytes() return base64.StdEncoding.EncodeToString(val), nil } ================================================ FILE: encoding/form/well_known_types_test.go ================================================ package form import ( "encoding/base64" "testing" "time" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) func TestMarshalTimeStamp(t *testing.T) { tests := []struct { input *timestamppb.Timestamp expect string }{ { input: timestamppb.New(time.Date(2022, 1, 2, 3, 4, 5, 6, time.Local)), expect: "2022-01-02T03:04:05.000000006Z", }, { input: timestamppb.New(time.Date(2022, 13, 1, 13, 61, 61, 100, time.Local)), expect: "2023-01-01T14:02:01.000000100Z", }, } for _, v := range tests { got, err := marshalTimestamp(v.input.ProtoReflect()) if err != nil { t.Fatal(err) } if want := v.expect; got != want { t.Errorf("expect %v, got %v", want, got) } } } func TestMarshalDuration(t *testing.T) { tests := []struct { input *durationpb.Duration expect string }{ { input: durationpb.New(time.Duration(1<<63 - 1)), expect: "2562047h47m16.854775807s", }, { input: durationpb.New(time.Duration(-1 << 63)), expect: "-2562047h47m16.854775808s", }, { input: durationpb.New(100 * time.Second), expect: "1m40s", }, { input: durationpb.New(-100 * time.Second), expect: "-1m40s", }, } for _, v := range tests { got, err := marshalDuration(v.input.ProtoReflect()) if err != nil { t.Fatal(err) } if want := v.expect; got != want { t.Errorf("expect %s, got %s", want, got) } } } func TestMarshalBytes(t *testing.T) { tests := []struct { input protoreflect.Message expect string }{ { input: wrapperspb.Bytes([]byte("abc123!?$*&()'-=@~")).ProtoReflect(), expect: base64.StdEncoding.EncodeToString([]byte("abc123!?$*&()'-=@~")), }, { input: wrapperspb.Bytes([]byte("kratos")).ProtoReflect(), expect: base64.StdEncoding.EncodeToString([]byte("kratos")), }, } for _, v := range tests { got, err := marshalBytes(v.input) if err != nil { t.Fatal(err) } if want := v.expect; got != want { t.Errorf("expect %v, got %v", want, got) } } } ================================================ FILE: encoding/json/json.go ================================================ package json import ( "encoding/json" "reflect" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/encoding" ) // Name is the name registered for the json codec. const Name = "json" var ( // MarshalOptions is a configurable JSON format marshaller. MarshalOptions = protojson.MarshalOptions{ EmitUnpopulated: true, } // UnmarshalOptions is a configurable JSON format parser. UnmarshalOptions = protojson.UnmarshalOptions{ DiscardUnknown: true, } ) func init() { encoding.RegisterCodec(codec{}) } // codec is a Codec implementation with json. type codec struct{} func (codec) Marshal(v any) ([]byte, error) { switch m := v.(type) { case json.Marshaler: return m.MarshalJSON() case proto.Message: return MarshalOptions.Marshal(m) default: return json.Marshal(m) } } func (codec) Unmarshal(data []byte, v any) error { switch m := v.(type) { case json.Unmarshaler: return m.UnmarshalJSON(data) case proto.Message: return UnmarshalOptions.Unmarshal(data, m) default: rv := reflect.ValueOf(v) for rv := rv; rv.Kind() == reflect.Ptr; { if rv.IsNil() { rv.Set(reflect.New(rv.Type().Elem())) } rv = rv.Elem() } if m, ok := reflect.Indirect(rv).Interface().(proto.Message); ok { return UnmarshalOptions.Unmarshal(data, m) } return json.Unmarshal(data, m) } } func (codec) Name() string { return Name } ================================================ FILE: encoding/json/json_test.go ================================================ package json import ( "encoding/json" "reflect" "strings" "testing" testData "github.com/go-kratos/kratos/v2/internal/testdata/encoding" ) type testEmbed struct { Level1a int `json:"a"` Level1b int `json:"b"` Level1c int `json:"c"` } type testMessage struct { Field1 string `json:"a"` Field2 string `json:"b"` Field3 string `json:"c"` Embed *testEmbed `json:"embed,omitempty"` } type mock struct { value int } const ( Unknown = iota Gopher Zebra ) func (a *mock) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } switch strings.ToLower(s) { default: a.value = Unknown case "gopher": a.value = Gopher case "zebra": a.value = Zebra } return nil } func (a *mock) MarshalJSON() ([]byte, error) { var s string switch a.value { default: s = "unknown" case Gopher: s = "gopher" case Zebra: s = "zebra" } return json.Marshal(s) } func TestJSON_Marshal(t *testing.T) { tests := []struct { input any expect string }{ { input: &testMessage{}, expect: `{"a":"","b":"","c":""}`, }, { input: &testMessage{Field1: "a", Field2: "b", Field3: "c"}, expect: `{"a":"a","b":"b","c":"c"}`, }, { input: &testData.TestModel{Id: 1, Name: "go-kratos", Hobby: []string{"1", "2"}}, expect: `{"id":"1","name":"go-kratos","hobby":["1","2"],"attrs":{}}`, }, { input: &mock{value: Gopher}, expect: `"gopher"`, }, } for _, v := range tests { data, err := (codec{}).Marshal(v.input) if err != nil { t.Errorf("marshal(%#v): %s", v.input, err) } if got, want := string(data), v.expect; strings.ReplaceAll(got, " ", "") != want { if strings.Contains(want, "\n") { t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", v.input, got, want) } else { t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want) } } } } func TestJSON_Unmarshal(t *testing.T) { p := testMessage{} p2 := testData.TestModel{} p3 := &testData.TestModel{} p4 := &mock{} tests := []struct { input string expect any }{ { input: `{"a":"","b":"","c":""}`, expect: &testMessage{}, }, { input: `{"a":"a","b":"b","c":"c"}`, expect: &p, }, { input: `{"id":"1","name":"go-kratos","hobby":["1","2"],"attrs":{}}`, expect: &p2, }, { input: `{"id":1,"name":"go-kratos","hobby":["1","2"]}`, expect: &p3, }, { input: `"zebra"`, expect: p4, }, } for _, v := range tests { want := []byte(v.input) err := (codec{}).Unmarshal(want, v.expect) if err != nil { t.Errorf("marshal(%#v): %s", v.input, err) } got, err := codec{}.Marshal(v.expect) if err != nil { t.Errorf("marshal(%#v): %s", v.input, err) } if !reflect.DeepEqual(strings.ReplaceAll(string(got), " ", ""), strings.ReplaceAll(string(want), " ", "")) { t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", v.input, got, want) } } } ================================================ FILE: encoding/proto/proto.go ================================================ // Package proto defines the protobuf codec. Importing this package will // register the codec. package proto import ( "errors" "reflect" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/encoding" ) // Name is the name registered for the proto compressor. const Name = "proto" func init() { encoding.RegisterCodec(codec{}) } // codec is a Codec implementation with protobuf. It is the default codec for Transport. type codec struct{} func (codec) Marshal(v any) ([]byte, error) { return proto.Marshal(v.(proto.Message)) } func (codec) Unmarshal(data []byte, v any) error { pm, err := getProtoMessage(v) if err != nil { return err } return proto.Unmarshal(data, pm) } func (codec) Name() string { return Name } func getProtoMessage(v any) (proto.Message, error) { if msg, ok := v.(proto.Message); ok { return msg, nil } val := reflect.ValueOf(v) if val.Kind() != reflect.Ptr { return nil, errors.New("not proto message") } val = val.Elem() return getProtoMessage(val.Interface()) } ================================================ FILE: encoding/proto/proto_test.go ================================================ package proto import ( "reflect" "testing" testData "github.com/go-kratos/kratos/v2/internal/testdata/encoding" ) func TestName(t *testing.T) { c := new(codec) if !reflect.DeepEqual(c.Name(), "proto") { t.Errorf("no expect float_key value: %v, but got: %v", c.Name(), "proto") } } func TestCodec(t *testing.T) { c := new(codec) model := testData.TestModel{ Id: 1, Name: "kratos", Hobby: []string{"study", "eat", "play"}, } m, err := c.Marshal(&model) if err != nil { t.Errorf("Marshal() should be nil, but got %s", err) } var res testData.TestModel err = c.Unmarshal(m, &res) if err != nil { t.Errorf("Unmarshal() should be nil, but got %s", err) } if !reflect.DeepEqual(res.Id, model.Id) { t.Errorf("ID should be %d, but got %d", res.Id, model.Id) } if !reflect.DeepEqual(res.Name, model.Name) { t.Errorf("Name should be %s, but got %s", res.Name, model.Name) } if !reflect.DeepEqual(res.Hobby, model.Hobby) { t.Errorf("Hobby should be %s, but got %s", res.Hobby, model.Hobby) } } func TestCodec2(t *testing.T) { c := new(codec) model := testData.TestModel{ Id: 1, Name: "kratos", Hobby: []string{"study", "eat", "play"}, } m, err := c.Marshal(&model) if err != nil { t.Errorf("Marshal() should be nil, but got %s", err) } var res testData.TestModel rp := &res err = c.Unmarshal(m, &rp) if err != nil { t.Errorf("Unmarshal() should be nil, but got %s", err) } if !reflect.DeepEqual(res.Id, model.Id) { t.Errorf("ID should be %d, but got %d", res.Id, model.Id) } if !reflect.DeepEqual(res.Name, model.Name) { t.Errorf("Name should be %s, but got %s", res.Name, model.Name) } if !reflect.DeepEqual(res.Hobby, model.Hobby) { t.Errorf("Hobby should be %s, but got %s", res.Hobby, model.Hobby) } } func Test_getProtoMessage(t *testing.T) { p := &testData.TestModel{Id: 1} type args struct { v any } tests := []struct { name string args args wantErr bool }{ {name: "test1", args: args{v: &testData.TestModel{}}, wantErr: false}, {name: "test2", args: args{v: testData.TestModel{}}, wantErr: true}, {name: "test3", args: args{v: &p}, wantErr: false}, {name: "test4", args: args{v: 1}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := getProtoMessage(tt.args.v) if (err != nil) != tt.wantErr { t.Errorf("getProtoMessage() error = %v, wantErr %v", err, tt.wantErr) return } }) } } ================================================ FILE: encoding/xml/xml.go ================================================ package xml import ( "encoding/xml" "github.com/go-kratos/kratos/v2/encoding" ) // Name is the name registered for the xml codec. const Name = "xml" func init() { encoding.RegisterCodec(codec{}) } // codec is a Codec implementation with xml. type codec struct{} func (codec) Marshal(v any) ([]byte, error) { return xml.Marshal(v) } func (codec) Unmarshal(data []byte, v any) error { return xml.Unmarshal(data, v) } func (codec) Name() string { return Name } ================================================ FILE: encoding/xml/xml_test.go ================================================ package xml import ( "reflect" "strings" "testing" ) type Plain struct { V any } type NestedOrder struct { XMLName struct{} `xml:"result"` Field1 string `xml:"parent>c"` Field2 string `xml:"parent>b"` Field3 string `xml:"parent>a"` } func TestCodec_Marshal(t *testing.T) { tests := []struct { Value any ExpectXML string }{ // Test value types {Value: &Plain{true}, ExpectXML: `true`}, {Value: &Plain{false}, ExpectXML: `false`}, {Value: &Plain{42}, ExpectXML: `42`}, { Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, ExpectXML: `` + `` + `C` + `B` + `A` + `` + ``, }, } for _, tt := range tests { data, err := (codec{}).Marshal(tt.Value) if err != nil { t.Errorf("marshal(%#v): %s", tt.Value, err) } if got, want := string(data), tt.ExpectXML; got != want { if strings.Contains(want, "\n") { t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", tt.Value, got, want) } else { t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", tt.Value, got, want) } } } } func TestCodec_Unmarshal(t *testing.T) { tests := []struct { want any InputXML string }{ { want: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, InputXML: `` + `` + `C` + `B` + `A` + `` + ``, }, } for _, tt := range tests { vt := reflect.TypeOf(tt.want) dest := reflect.New(vt.Elem()).Interface() data := []byte(tt.InputXML) err := (codec{}).Unmarshal(data, dest) if err != nil { t.Errorf("unmarshal(%#v, %#v): %s", tt.InputXML, dest, err) } if got, want := dest, tt.want; !reflect.DeepEqual(got, want) { t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", tt.InputXML, got, want) } } } func TestCodec_NilUnmarshal(t *testing.T) { tests := []struct { want any InputXML string }{ { want: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, InputXML: `` + `` + `C` + `B` + `A` + `` + ``, }, } for _, tt := range tests { s := struct { A string `xml:"a"` B *NestedOrder }{A: "a"} data := []byte(tt.InputXML) err := (codec{}).Unmarshal(data, &s.B) if err != nil { t.Errorf("unmarshal(%#v, %#v): %s", tt.InputXML, s.B, err) } if got, want := s.B, tt.want; !reflect.DeepEqual(got, want) { t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", tt.InputXML, got, want) } } } ================================================ FILE: encoding/yaml/yaml.go ================================================ package yaml import ( "gopkg.in/yaml.v3" "github.com/go-kratos/kratos/v2/encoding" ) // Name is the name registered for the yaml codec. const Name = "yaml" func init() { encoding.RegisterCodec(codec{}) } // codec is a Codec implementation with yaml. type codec struct{} func (codec) Marshal(v any) ([]byte, error) { return yaml.Marshal(v) } func (codec) Unmarshal(data []byte, v any) error { return yaml.Unmarshal(data, v) } func (codec) Name() string { return Name } ================================================ FILE: encoding/yaml/yaml_test.go ================================================ package yaml import ( "math" "reflect" "testing" ) func TestCodec_Unmarshal(t *testing.T) { tests := []struct { data string value any }{ { "", (*struct{})(nil), }, { "{}", &struct{}{}, }, { "v: hi", map[string]string{"v": "hi"}, }, { "v: hi", map[string]any{"v": "hi"}, }, { "v: true", map[string]string{"v": "true"}, }, { "v: true", map[string]any{"v": true}, }, { "v: 10", map[string]any{"v": 10}, }, { "v: 0b10", map[string]any{"v": 2}, }, { "v: 0xA", map[string]any{"v": 10}, }, { "v: 4294967296", map[string]int64{"v": 4294967296}, }, { "v: 0.1", map[string]any{"v": 0.1}, }, { "v: .1", map[string]any{"v": 0.1}, }, { "v: .Inf", map[string]any{"v": math.Inf(+1)}, }, { "v: -.Inf", map[string]any{"v": math.Inf(-1)}, }, { "v: -10", map[string]any{"v": -10}, }, { "v: -.1", map[string]any{"v": -0.1}, }, } for _, tt := range tests { v := reflect.ValueOf(tt.value).Type() value := reflect.New(v) err := (codec{}).Unmarshal([]byte(tt.data), value.Interface()) if err != nil { t.Fatalf("(codec{}).Unmarshal should not return err") } } spec := struct { A string B map[string]any }{A: "a"} err := (codec{}).Unmarshal([]byte("v: hi"), &spec.B) if err != nil { t.Fatalf("(codec{}).Unmarshal should not return err") } } func TestCodec_Marshal(t *testing.T) { value := map[string]string{"v": "hi"} got, err := (codec{}).Marshal(value) if err != nil { t.Fatalf("should not return err") } if string(got) != "v: hi\n" { t.Fatalf("want \"v: hi\n\" return \"%s\"", string(got)) } } ================================================ FILE: errors/errors.go ================================================ package errors import ( "errors" "fmt" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/status" httpstatus "github.com/go-kratos/kratos/v2/transport/http/status" ) const ( // UnknownCode is unknown code for error info. UnknownCode = 500 // UnknownReason is unknown reason for error info. UnknownReason = "" // SupportPackageIsVersion1 this constant should not be referenced by any other code. SupportPackageIsVersion1 = true ) // Error is a status error. type Error struct { Status cause error } func (e *Error) Error() string { return fmt.Sprintf("error: code = %d reason = %s message = %s metadata = %v cause = %v", e.Code, e.Reason, e.Message, e.Metadata, e.cause) } // Unwrap provides compatibility for Go 1.13 error chains. func (e *Error) Unwrap() error { return e.cause } // Is matches each error in the chain with the target value. func (e *Error) Is(err error) bool { if se := new(Error); errors.As(err, &se) { return se.Code == e.Code && se.Reason == e.Reason } return false } // WithCause with the underlying cause of the error. func (e *Error) WithCause(cause error) *Error { err := Clone(e) err.cause = cause return err } // WithMetadata with an MD formed by the mapping of key, value. func (e *Error) WithMetadata(md map[string]string) *Error { err := Clone(e) err.Metadata = md return err } // GRPCStatus returns the Status represented by se. func (e *Error) GRPCStatus() *status.Status { s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message). WithDetails(&errdetails.ErrorInfo{ Reason: e.Reason, Metadata: e.Metadata, }) return s } // New returns an error object for the code, message. func New(code int, reason, message string) *Error { return &Error{ Status: Status{ Code: int32(code), Message: message, Reason: reason, }, } } // Newf New(code fmt.Sprintf(format, a...)) func Newf(code int, reason, format string, a ...any) *Error { return New(code, reason, fmt.Sprintf(format, a...)) } // Errorf returns an error object for the code, message and error info. func Errorf(code int, reason, format string, a ...any) error { return New(code, reason, fmt.Sprintf(format, a...)) } // Code returns the http code for an error. // It supports wrapped errors. func Code(err error) int { if err == nil { return 200 //nolint:mnd } return int(FromError(err).Code) } // Reason returns the reason for a particular error. // It supports wrapped errors. func Reason(err error) string { if err == nil { return UnknownReason } return FromError(err).Reason } // Clone deep clone error to a new error. func Clone(err *Error) *Error { if err == nil { return nil } metadata := make(map[string]string, len(err.Metadata)) for k, v := range err.Metadata { metadata[k] = v } return &Error{ cause: err.cause, Status: Status{ Code: err.Code, Reason: err.Reason, Message: err.Message, Metadata: metadata, }, } } // FromError try to convert an error to *Error. // It supports wrapped errors. func FromError(err error) *Error { if err == nil { return nil } if se := new(Error); errors.As(err, &se) { return se } gs, ok := status.FromError(err) if !ok { return New(UnknownCode, UnknownReason, err.Error()) } ret := New( httpstatus.FromGRPCCode(gs.Code()), UnknownReason, gs.Message(), ) for _, detail := range gs.Details() { switch d := detail.(type) { case *errdetails.ErrorInfo: ret.Reason = d.Reason return ret.WithMetadata(d.Metadata) } } return ret } ================================================ FILE: errors/errors.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.0 // protoc v3.19.4 // source: errors/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 Status 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 *Status) Reset() { *x = Status{} if protoimpl.UnsafeEnabled { mi := &file_errors_errors_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Status) String() string { return protoimpl.X.MessageStringOf(x) } func (*Status) ProtoMessage() {} func (x *Status) ProtoReflect() protoreflect.Message { mi := &file_errors_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 Status.ProtoReflect.Descriptor instead. func (*Status) Descriptor() ([]byte, []int) { return file_errors_errors_proto_rawDescGZIP(), []int{0} } func (x *Status) GetCode() int32 { if x != nil { return x.Code } return 0 } func (x *Status) GetReason() string { if x != nil { return x.Reason } return "" } func (x *Status) GetMessage() string { if x != nil { return x.Message } return "" } func (x *Status) GetMetadata() map[string]string { if x != nil { return x.Metadata } return nil } var file_errors_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/errors.proto", }, { ExtendedType: (*descriptorpb.EnumValueOptions)(nil), ExtensionType: (*int32)(nil), Field: 1109, Name: "errors.code", Tag: "varint,1109,opt,name=code", Filename: "errors/errors.proto", }, } // Extension fields to descriptorpb.EnumOptions. var ( // optional int32 default_code = 1108; E_DefaultCode = &file_errors_errors_proto_extTypes[0] ) // Extension fields to descriptorpb.EnumValueOptions. var ( // optional int32 code = 1109; E_Code = &file_errors_errors_proto_extTypes[1] ) var File_errors_errors_proto protoreflect.FileDescriptor var file_errors_errors_proto_rawDesc = []byte{ 0x0a, 0x13, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2f, 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, 0xc5, 0x01, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x38, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 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_errors_proto_rawDescOnce sync.Once file_errors_errors_proto_rawDescData = file_errors_errors_proto_rawDesc ) func file_errors_errors_proto_rawDescGZIP() []byte { file_errors_errors_proto_rawDescOnce.Do(func() { file_errors_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_errors_errors_proto_rawDescData) }) return file_errors_errors_proto_rawDescData } var file_errors_errors_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_errors_errors_proto_goTypes = []interface{}{ (*Status)(nil), // 0: errors.Status nil, // 1: errors.Status.MetadataEntry (*descriptorpb.EnumOptions)(nil), // 2: google.protobuf.EnumOptions (*descriptorpb.EnumValueOptions)(nil), // 3: google.protobuf.EnumValueOptions } var file_errors_errors_proto_depIdxs = []int32{ 1, // 0: errors.Status.metadata:type_name -> errors.Status.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_errors_proto_init() } func file_errors_errors_proto_init() { if File_errors_errors_proto != nil { return } if !protoimpl.UnsafeEnabled { file_errors_errors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Status); 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_errors_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 2, NumServices: 0, }, GoTypes: file_errors_errors_proto_goTypes, DependencyIndexes: file_errors_errors_proto_depIdxs, MessageInfos: file_errors_errors_proto_msgTypes, ExtensionInfos: file_errors_errors_proto_extTypes, }.Build() File_errors_errors_proto = out.File file_errors_errors_proto_rawDesc = nil file_errors_errors_proto_goTypes = nil file_errors_errors_proto_depIdxs = nil } ================================================ FILE: errors/errors.proto ================================================ 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 Status { int32 code = 1; string reason = 2; string message = 3; map metadata = 4; }; extend google.protobuf.EnumOptions { int32 default_code = 1108; } extend google.protobuf.EnumValueOptions { int32 code = 1109; } ================================================ FILE: errors/errors_test.go ================================================ package errors import ( "errors" "fmt" "net/http" "reflect" "testing" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type TestError struct{ message string } func (e *TestError) Error() string { return e.message } func TestErrors(t *testing.T) { var base *Error err := Newf(http.StatusBadRequest, "reason", "message") err2 := Newf(http.StatusBadRequest, "reason", "message") err3 := err.WithMetadata(map[string]string{ "foo": "bar", }) werr := fmt.Errorf("wrap %w", err) if errors.Is(err, new(Error)) { t.Errorf("should not be equal: %v", err) } if !errors.Is(werr, err) { t.Errorf("should be equal: %v", err) } if !errors.Is(werr, err2) { t.Errorf("should be equal: %v", err) } if !errors.As(err, &base) { t.Errorf("should be matches: %v", err) } if !IsBadRequest(err) { t.Errorf("should be matches: %v", err) } if reason := Reason(err); reason != err3.Reason { t.Errorf("got %s want: %s", reason, err) } if err3.Metadata["foo"] != "bar" { t.Error("not expected metadata") } gs := err.GRPCStatus() se := FromError(gs.Err()) if se.Reason != "reason" { t.Errorf("got %+v want %+v", se, err) } gs2 := status.New(codes.InvalidArgument, "bad request") se2 := FromError(gs2.Err()) // codes.InvalidArgument should convert to http.StatusBadRequest if se2.Code != http.StatusBadRequest { t.Errorf("convert code err, got %d want %d", UnknownCode, http.StatusBadRequest) } if FromError(nil) != nil { t.Errorf("FromError(nil) should be nil") } e := FromError(errors.New("test")) if !reflect.DeepEqual(e.Code, int32(UnknownCode)) { t.Errorf("no expect value: %v, but got: %v", e.Code, int32(UnknownCode)) } } func TestIs(t *testing.T) { tests := []struct { name string e *Error err error want bool }{ { name: "true", e: New(404, "test", ""), err: New(http.StatusNotFound, "test", ""), want: true, }, { name: "false", e: New(0, "test", ""), err: errors.New("test"), want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if ok := tt.e.Is(tt.err); ok != tt.want { t.Errorf("Error.Error() = %v, want %v", ok, tt.want) } }) } } func TestCause(t *testing.T) { testError := &TestError{message: "test"} err := BadRequest("foo", "bar").WithCause(testError) if !errors.Is(err, testError) { t.Fatalf("want %v but got %v", testError, err) } if te := new(TestError); errors.As(err, &te) { if te.message != testError.message { t.Fatalf("want %s but got %s", testError.message, te.message) } } } func TestOther(t *testing.T) { err := Errorf(10001, "test code 10001", "message") // Code if !reflect.DeepEqual(Code(nil), 200) { t.Errorf("Code(nil) = %v, want %v", Code(nil), 200) } if !reflect.DeepEqual(Code(errors.New("test")), UnknownCode) { t.Errorf(`Code(errors.New("test")) = %v, want %v`, Code(nil), 200) } if !reflect.DeepEqual(Code(err), 10001) { t.Errorf(`Code(err) = %v, want %v`, Code(err), 10001) } // Reason if !reflect.DeepEqual(Reason(nil), UnknownReason) { t.Errorf(`Reason(nil) = %v, want %v`, Reason(nil), UnknownReason) } if !reflect.DeepEqual(Reason(errors.New("test")), UnknownReason) { t.Errorf(`Reason(errors.New("test")) = %v, want %v`, Reason(nil), UnknownReason) } if !reflect.DeepEqual(Reason(err), "test code 10001") { t.Errorf(`Reason(err) = %v, want %v`, Reason(err), "test code 10001") } // Clone err400 := Newf(http.StatusBadRequest, "BAD_REQUEST", "param invalid") err400.Metadata = map[string]string{ "key1": "val1", "key2": "val2", } if cerr := Clone(err400); cerr == nil || cerr.Error() != err400.Error() { t.Errorf("Clone(err) = %v, want %v", Clone(err400), err400) } if cerr := Clone(nil); cerr != nil { t.Errorf("Clone(nil) = %v, want %v", Clone(err400), err400) } } ================================================ FILE: errors/types.go ================================================ // nolint:mnd package errors // BadRequest new BadRequest error that is mapped to a 400 response. func BadRequest(reason, message string) *Error { return New(400, reason, message) } // IsBadRequest determines if err is an error which indicates a BadRequest error. // It supports wrapped errors. func IsBadRequest(err error) bool { return Code(err) == 400 } // Unauthorized new Unauthorized error that is mapped to a 401 response. func Unauthorized(reason, message string) *Error { return New(401, reason, message) } // IsUnauthorized determines if err is an error which indicates an Unauthorized error. // It supports wrapped errors. func IsUnauthorized(err error) bool { return Code(err) == 401 } // Forbidden new Forbidden error that is mapped to a 403 response. func Forbidden(reason, message string) *Error { return New(403, reason, message) } // IsForbidden determines if err is an error which indicates a Forbidden error. // It supports wrapped errors. func IsForbidden(err error) bool { return Code(err) == 403 } // NotFound new NotFound error that is mapped to a 404 response. func NotFound(reason, message string) *Error { return New(404, reason, message) } // IsNotFound determines if err is an error which indicates an NotFound error. // It supports wrapped errors. func IsNotFound(err error) bool { return Code(err) == 404 } // Conflict new Conflict error that is mapped to a 409 response. func Conflict(reason, message string) *Error { return New(409, reason, message) } // IsConflict determines if err is an error which indicates a Conflict error. // It supports wrapped errors. func IsConflict(err error) bool { return Code(err) == 409 } // TooManyRequests new TooManyRequests error that is mapped to an HTTP 429 response. func TooManyRequests(reason, message string) *Error { return New(429, reason, message) } // IsTooManyRequests determines if err is an error which indicates a TooManyRequests error. // It supports wrapped errors. func IsTooManyRequests(err error) bool { return Code(err) == 429 } // ClientClosed new ClientClosed error that is mapped to an HTTP 499 response. func ClientClosed(reason, message string) *Error { return New(499, reason, message) } // IsClientClosed determines if err is an error which indicates a IsClientClosed error. // It supports wrapped errors. func IsClientClosed(err error) bool { return Code(err) == 499 } // InternalServer new InternalServer error that is mapped to a 500 response. func InternalServer(reason, message string) *Error { return New(500, reason, message) } // IsInternalServer determines if err is an error which indicates an Internal error. // It supports wrapped errors. func IsInternalServer(err error) bool { return Code(err) == 500 } // ServiceUnavailable new ServiceUnavailable error that is mapped to an HTTP 503 response. func ServiceUnavailable(reason, message string) *Error { return New(503, reason, message) } // IsServiceUnavailable determines if err is an error which indicates an Unavailable error. // It supports wrapped errors. func IsServiceUnavailable(err error) bool { return Code(err) == 503 } // GatewayTimeout new GatewayTimeout error that is mapped to an HTTP 504 response. func GatewayTimeout(reason, message string) *Error { return New(504, reason, message) } // IsGatewayTimeout determines if err is an error which indicates a GatewayTimeout error. // It supports wrapped errors. func IsGatewayTimeout(err error) bool { return Code(err) == 504 } ================================================ FILE: errors/types_test.go ================================================ package errors import ( "testing" ) func TestTypes(t *testing.T) { var ( input = []error{ BadRequest("reason_400", "message_400"), Unauthorized("reason_401", "message_401"), Forbidden("reason_403", "message_403"), NotFound("reason_404", "message_404"), Conflict("reason_409", "message_409"), InternalServer("reason_500", "message_500"), ServiceUnavailable("reason_503", "message_503"), GatewayTimeout("reason_504", "message_504"), ClientClosed("reason_499", "message_499"), } output = []func(error) bool{ IsBadRequest, IsUnauthorized, IsForbidden, IsNotFound, IsConflict, IsInternalServer, IsServiceUnavailable, IsGatewayTimeout, IsClientClosed, } ) for i, in := range input { if !output[i](in) { t.Errorf("not expect: %v", in) } } } ================================================ FILE: errors/wrap.go ================================================ package errors import ( stderrors "errors" ) // Is reports whether any error in err's chain matches target. // // The chain consists of err itself followed by the sequence of errors obtained by // repeatedly calling Unwrap. // // An error is considered to match a target if it is equal to that target or if // it implements a method Is(error) bool such that Is(target) returns true. func Is(err, target error) bool { return stderrors.Is(err, target) } // As finds the first error in err's chain that matches target, and if so, sets // target to that error value and returns true. // // The chain consists of err itself followed by the sequence of errors obtained by // repeatedly calling Unwrap. // // An error matches target if the error's concrete value is assignable to the value // pointed to by target, or if the error has a method As(interface{}) bool such that // As(target) returns true. In the latter case, the As method is responsible for // setting target. // // As will panic if target is not a non-nil pointer to either a type that implements // error, or to any interface type. As returns false if err is nil. func As(err error, target any) bool { return stderrors.As(err, target) } // Unwrap returns the result of calling the Unwrap method on err, if err's // type contains an Unwrap method returning error. // Otherwise, Unwrap returns nil. func Unwrap(err error) error { return stderrors.Unwrap(err) } ================================================ FILE: errors/wrap_test.go ================================================ package errors import ( "fmt" "testing" ) type mockErr struct{} func (*mockErr) Error() string { return "mock error" } func TestWarp(t *testing.T) { var err error = &mockErr{} err2 := fmt.Errorf("wrap %w", err) if err != Unwrap(err2) { t.Errorf("got %v want: %v", err, Unwrap(err2)) } if !Is(err2, err) { t.Errorf("Is(err2, err) got %v want: %v", Is(err2, err), true) } err3 := &mockErr{} if !As(err2, &err3) { t.Errorf("As(err2, &err3) got %v want: %v", As(err2, &err3), true) } } ================================================ FILE: go.mod ================================================ module github.com/go-kratos/kratos/v2 go 1.22 require ( dario.cat/mergo v1.0.0 github.com/fsnotify/fsnotify v1.6.0 github.com/go-kratos/aegis v0.2.0 github.com/go-playground/form/v4 v4.2.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.4.0 github.com/gorilla/mux v1.8.1 go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/metric v1.24.0 go.opentelemetry.io/otel/sdk v1.24.0 go.opentelemetry.io/otel/sdk/metric v1.24.0 go.opentelemetry.io/otel/trace v1.24.0 golang.org/x/sync v0.10.0 google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 google.golang.org/grpc v1.61.1 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/shirou/gopsutil/v3 v3.23.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) retract v2.9.0 ================================================ FILE: go.sum ================================================ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb h1:kxNVXsNro/lpR5WD+P1FI/yUHn2G03Glber3k8cQL2Y= github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb/go.mod h1:GxGqnjWzl1Gz8WfAfMJSfhvsi4EPZayRb25nLHDWXyA= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08= github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: hack/.lintcheck_failures ================================================ ================================================ FILE: hack/.test_ignored_files ================================================ ./examples ./cmd/protoc-gen-go-errors ./cmd/protoc-gen-go-http ./cmd/kratos ./contrib/config/kubernetes ./contrib/registry/kubernetes ./contrib/registry/zookeeper ./contrib/registry/eureka ================================================ FILE: hack/resolve-modules.sh ================================================ #!/usr/bin/env bash # This is used by the linter action. # Recursively finds all directories with a go.mod file and creates # a GitHub Actions JSON output option. set -o errexit echo "Resolving modules in $(pwd)" KRATOS_HOME=$( cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd ) source "${KRATOS_HOME}/hack/util.sh" FAILURE_FILE=${KRATOS_HOME}/hack/.lintcheck_failures all_modules=$(util::find_modules) failing_modules=() while IFS='' read -r line; do failing_modules+=("$line"); done < <(cat "$FAILURE_FILE") echo "Ignored failing modules:" echo "${failing_modules[*]}" echo PATHS="" for mod in $all_modules; do util::array_contains "$mod" "${failing_modules[*]}" && in_failing=$? || in_failing=$? if [[ "$in_failing" -ne "0" ]]; then PATHS+=$(printf '{"workdir":"%s"},' ${mod}) fi done echo "::set-output name=matrix::{\"include\":[${PATHS%?}]}" ================================================ FILE: hack/tools.sh ================================================ #!/usr/bin/env bash # This is a tools shell script # used by Makefile commands set -o errexit set -o nounset set -o pipefail GO111MODULE=on KRATOS_HOME=$( cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd ) source "${KRATOS_HOME}/hack/util.sh" LINTER=${KRATOS_HOME}/bin/golangci-lint LINTER_CONFIG=${KRATOS_HOME}/.golangci.yml FAILURE_FILE=${KRATOS_HOME}/hack/.lintcheck_failures IGNORED_FILE=${KRATOS_HOME}/hack/.test_ignored_files all_modules=$(util::find_modules) failing_modules=() while IFS='' read -r line; do failing_modules+=("$line"); done < <(cat "$FAILURE_FILE") ignored_modules=() while IFS='' read -r line; do ignored_modules+=("$line"); done < <(cat "$IGNORED_FILE") # functions # lint all mod function lint() { for mod in $all_modules; do local in_failing util::array_contains "$mod" "${failing_modules[*]}" && in_failing=$? || in_failing=$? if [[ "$in_failing" -ne "0" ]]; then pushd "$mod" >/dev/null && echo "golangci lint $(sed -n 1p go.mod | cut -d ' ' -f2)" && eval "${LINTER} run --timeout=5m --config=${LINTER_CONFIG}" popd >/dev/null || exit fi done } # test all mod function test() { for mod in $all_modules; do local in_failing util::array_contains "$mod" "${ignored_modules[*]}" && in_failing=$? || in_failing=$? if [[ "$in_failing" -ne "0" ]]; then pushd "$mod" >/dev/null && echo "go test $(sed -n 1p go.mod | cut -d ' ' -f2)" && go test -race ./... popd >/dev/null || exit fi done } function test_coverage() { echo "" >coverage.out local base base=$(pwd) for mod in $all_modules; do local in_failing util::array_contains "$mod" "${ignored_modules[*]}" && in_failing=$? || in_failing=$? if [[ "$in_failing" -ne "0" ]]; then pushd "$mod" >/dev/null && echo "go test $(sed -n 1p go.mod | cut -d ' ' -f2)" && go test -race -coverprofile=profile.out -covermode=atomic ./... if [ -f profile.out ]; then cat profile.out >>"${base}/coverage.out" rm profile.out fi popd >/dev/null || exit fi done } # try to fix all mod with golangci-lint function fix() { for mod in $all_modules; do local in_failing util::array_contains "$mod" "${failing_modules[*]}" && in_failing=$? || in_failing=$? if [[ "$in_failing" -ne "0" ]]; then pushd "$mod" >/dev/null && echo "golangci fix $(sed -n 1p go.mod | cut -d ' ' -f2)" && eval "${LINTER} run -v --fix --timeout=5m --config=${LINTER_CONFIG}" popd >/dev/null || exit fi done } function tidy() { for mod in $all_modules; do pushd "$mod" >/dev/null && echo "go mod tidy $(sed -n 1p go.mod | cut -d ' ' -f2)" && go mod tidy popd >/dev/null || exit done } function help() { echo "use: lint, test, test_coverage, fix, tidy" } case $1 in lint) lint ;; test) test ;; test_coverage) test_coverage ;; tidy) tidy ;; fix) fix ;; *) help ;; esac ================================================ FILE: hack/util.sh ================================================ #!/usr/bin/env bash # This is a common util functions shell script # arguments: target, item1, item2, item3, ... # returns 0 if target is in the given items, 1 otherwise. function util::array_contains() { local target="$1" shift local items="$*" for item in ${items[*]}; do if [[ "${item}" == "${target}" ]]; then return 0 fi done return 1 } # find all go mod path # returns an array contains mod path function util::find_modules() { find . -not \( \ \( \ -path './output' \ -o -path './.git' \ -o -path '*/third_party/*' \ -o -path '*/vendor/*' \ \) -prune \ \) -name 'go.mod' -print0 | xargs -0 -I {} dirname {} } ================================================ FILE: internal/README.md ================================================ # internal ================================================ FILE: internal/context/context.go ================================================ package context import ( "context" "sync" "sync/atomic" "time" ) type mergeCtx struct { parent1, parent2 context.Context done chan struct{} doneMark atomic.Bool doneOnce sync.Once doneErr error cancelCh chan struct{} cancelOnce sync.Once } // Merge merges two contexts into one. func Merge(parent1, parent2 context.Context) (context.Context, context.CancelFunc) { mc := &mergeCtx{ parent1: parent1, parent2: parent2, done: make(chan struct{}), cancelCh: make(chan struct{}), } select { case <-parent1.Done(): _ = mc.finish(parent1.Err()) case <-parent2.Done(): _ = mc.finish(parent2.Err()) default: go mc.wait() } return mc, mc.cancel } func (mc *mergeCtx) finish(err error) error { mc.doneOnce.Do(func() { mc.doneErr = err mc.doneMark.Store(true) close(mc.done) }) return mc.doneErr } func (mc *mergeCtx) wait() { var err error select { case <-mc.parent1.Done(): err = mc.parent1.Err() case <-mc.parent2.Done(): err = mc.parent2.Err() case <-mc.cancelCh: err = context.Canceled } _ = mc.finish(err) } func (mc *mergeCtx) cancel() { mc.cancelOnce.Do(func() { close(mc.cancelCh) }) } // Done implements context.Context. func (mc *mergeCtx) Done() <-chan struct{} { return mc.done } // Err implements context.Context. func (mc *mergeCtx) Err() error { if mc.doneMark.Load() { return mc.doneErr } var err error select { case <-mc.parent1.Done(): err = mc.parent1.Err() case <-mc.parent2.Done(): err = mc.parent2.Err() case <-mc.cancelCh: err = context.Canceled default: return nil } return mc.finish(err) } // Deadline implements context.Context. func (mc *mergeCtx) Deadline() (time.Time, bool) { d1, ok1 := mc.parent1.Deadline() d2, ok2 := mc.parent2.Deadline() switch { case !ok1: return d2, ok2 case !ok2: return d1, ok1 case d1.Before(d2): return d1, true default: return d2, true } } // Value implements context.Context. func (mc *mergeCtx) Value(key any) any { if v := mc.parent1.Value(key); v != nil { return v } return mc.parent2.Value(key) } ================================================ FILE: internal/context/context_test.go ================================================ package context import ( "context" "errors" "reflect" "testing" "time" ) func TestContext(t *testing.T) { type ctxKey1 struct{} type ctxKey2 struct{} ctx1 := context.WithValue(context.Background(), ctxKey1{}, "https://github.com/go-kratos/") ctx2 := context.WithValue(context.Background(), ctxKey2{}, "https://go-kratos.dev/") ctx, cancel := Merge(ctx1, ctx2) defer cancel() got := ctx.Value(ctxKey1{}) value1, ok := got.(string) if !ok { t.Errorf("expect %v, got %v", true, ok) } if !reflect.DeepEqual(value1, "https://github.com/go-kratos/") { t.Errorf("expect %v, got %v", "https://github.com/go-kratos/", value1) } got2 := ctx.Value(ctxKey2{}) value2, ok := got2.(string) if !ok { t.Errorf("expect %v, got %v", true, ok) } if !reflect.DeepEqual("https://go-kratos.dev/", value2) { t.Errorf("expect %v, got %v", "https://go-kratos.dev/", value2) } t.Log(value1) t.Log(value2) } func TestMerge(t *testing.T) { type ctxKey1 struct{} type ctxKey2 struct{} ctx, cancel := context.WithCancel(context.Background()) cancel() ctx1 := context.WithValue(context.Background(), ctxKey1{}, "https://github.com/go-kratos/") ctx2 := context.WithValue(ctx, ctxKey2{}, "https://go-kratos.dev/") ctx, cancel = Merge(ctx1, ctx2) defer cancel() got := ctx.Value(ctxKey1{}) value1, ok := got.(string) if !ok { t.Errorf("expect %v, got %v", true, ok) } if !reflect.DeepEqual(value1, "https://github.com/go-kratos/") { t.Errorf("expect %v, got %v", "https://github.com/go-kratos/", value1) } got2 := ctx.Value(ctxKey2{}) value2, ok := got2.(string) if !ok { t.Errorf("expect %v, got %v", true, ok) } if !reflect.DeepEqual(value2, "https://go-kratos.dev/") { t.Errorf("expect %v, got %v", " https://go-kratos.dev/", value2) } t.Log(ctx) } func TestErr(t *testing.T) { ctx1, cancel := context.WithTimeout(context.Background(), time.Microsecond) defer cancel() time.Sleep(time.Millisecond) ctx, cancel := Merge(ctx1, context.Background()) defer cancel() if !errors.Is(ctx.Err(), context.DeadlineExceeded) { t.Errorf("expect %v, got %v", context.DeadlineExceeded, ctx.Err()) } } func TestDone(t *testing.T) { ctx1, cancel := context.WithCancel(context.Background()) defer cancel() ctx, cancel := Merge(ctx1, context.Background()) go func() { time.Sleep(time.Millisecond * 50) cancel() }() if <-ctx.Done() != struct{}{} { t.Errorf("expect %v, got %v", struct{}{}, <-ctx.Done()) } } func TestFinish(t *testing.T) { mc := &mergeCtx{ parent1: context.Background(), parent2: context.Background(), done: make(chan struct{}), cancelCh: make(chan struct{}), } err := mc.finish(context.DeadlineExceeded) if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("expect %v, got %v", context.DeadlineExceeded, err) } if done := mc.doneMark.Load(); done != true { t.Errorf("expect %v, got %v", true, done) } if <-mc.done != struct{}{} { t.Errorf("expect %v, got %v", struct{}{}, <-mc.done) } } func TestWait(t *testing.T) { ctx1, cancel := context.WithCancel(context.Background()) mc := &mergeCtx{ parent1: ctx1, parent2: context.Background(), done: make(chan struct{}), cancelCh: make(chan struct{}), } go func() { time.Sleep(time.Millisecond * 50) cancel() }() mc.wait() t.Log(mc.doneErr) if !errors.Is(mc.doneErr, context.Canceled) { t.Errorf("expect %v, got %v", context.Canceled, mc.doneErr) } ctx2, cancel2 := context.WithCancel(context.Background()) mc = &mergeCtx{ parent1: ctx2, parent2: context.Background(), done: make(chan struct{}), cancelCh: make(chan struct{}), } go func() { time.Sleep(time.Millisecond * 50) cancel2() }() mc.wait() t.Log(mc.doneErr) if !errors.Is(mc.doneErr, context.Canceled) { t.Errorf("expect %v, got %v", context.Canceled, mc.doneErr) } } func TestCancel(t *testing.T) { mc := &mergeCtx{ parent1: context.Background(), parent2: context.Background(), done: make(chan struct{}), cancelCh: make(chan struct{}), } mc.cancel() if <-mc.cancelCh != struct{}{} { t.Errorf("expect %v, got %v", struct{}{}, <-mc.cancelCh) } } func Test_mergeCtx_Deadline(t *testing.T) { type fields struct { parent1Timeout time.Time parent2Timeout time.Time } tests := []struct { name string fields fields want1 bool }{ { name: "parent1 not deadline", fields: fields{time.Time{}, time.Now().Add(time.Second * 100)}, want1: true, }, { name: "parent2 not deadline", fields: fields{time.Now().Add(time.Second * 100), time.Time{}}, want1: true, }, { name: " parent1 parent2 not deadline", fields: fields{time.Time{}, time.Time{}}, want1: false, }, { name: " parent1 < parent2", fields: fields{time.Now().Add(time.Second * 100), time.Now().Add(time.Second * 200)}, want1: true, }, { name: " parent1 > parent2", fields: fields{time.Now().Add(time.Second * 100), time.Now().Add(time.Second * 50)}, want1: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var parent1, parent2 context.Context var cancel1, cancel2 context.CancelFunc if reflect.DeepEqual(tt.fields.parent1Timeout, time.Time{}) { parent1 = context.Background() } else { parent1, cancel1 = context.WithDeadline(context.Background(), tt.fields.parent1Timeout) defer cancel1() } if reflect.DeepEqual(tt.fields.parent2Timeout, time.Time{}) { parent2 = context.Background() } else { parent2, cancel2 = context.WithDeadline(context.Background(), tt.fields.parent2Timeout) defer cancel2() } mc := &mergeCtx{ parent1: parent1, parent2: parent2, } got, got1 := mc.Deadline() t.Log(got) if got1 != tt.want1 { t.Errorf("Deadline() got1 = %v, want %v", got1, tt.want1) } }) } } func Test_Err2(t *testing.T) { ctx1, cancel := context.WithCancel(context.Background()) defer cancel() time.Sleep(time.Millisecond) ctx, cancel := Merge(ctx1, context.Background()) defer cancel() if ctx.Err() != nil { t.Errorf("expect %v, got %v", nil, ctx.Err()) } ctx1, cancel1 := context.WithCancel(context.Background()) time.Sleep(time.Millisecond) ctx, cancel = Merge(ctx1, context.Background()) defer cancel() cancel1() if !errors.Is(ctx.Err(), context.Canceled) { t.Errorf("expect %v, got %v", context.Canceled, ctx.Err()) } ctx1, cancel1 = context.WithCancel(context.Background()) time.Sleep(time.Millisecond) ctx, cancel = Merge(context.Background(), ctx1) defer cancel() cancel1() if !errors.Is(ctx.Err(), context.Canceled) { t.Errorf("expect %v, got %v", context.Canceled, ctx.Err()) } ctx, cancel = Merge(context.Background(), context.Background()) cancel() if !errors.Is(ctx.Err(), context.Canceled) { t.Errorf("expect %v, got %v", context.Canceled, ctx.Err()) } } ================================================ FILE: internal/endpoint/endpoint.go ================================================ package endpoint import ( "net/url" ) // NewEndpoint new an Endpoint URL. func NewEndpoint(scheme, host string) *url.URL { return &url.URL{Scheme: scheme, Host: host} } // ParseEndpoint parses an Endpoint URL. func ParseEndpoint(endpoints []string, scheme string) (string, error) { for _, e := range endpoints { u, err := url.Parse(e) if err != nil { return "", err } if u.Scheme == scheme { return u.Host, nil } } return "", nil } // Scheme is the scheme of endpoint url. // examples: scheme="http",isSecure=true get "https" func Scheme(scheme string, isSecure bool) string { if isSecure { return scheme + "s" } return scheme } ================================================ FILE: internal/endpoint/endpoint_test.go ================================================ package endpoint import ( "net/url" "reflect" "testing" ) func TestNewEndpoint(t *testing.T) { type args struct { scheme string host string } tests := []struct { name string args args want *url.URL }{ { name: "https://github.com/go-kratos/kratos/", args: args{"https", "github.com/go-kratos/kratos/"}, want: &url.URL{Scheme: "https", Host: "github.com/go-kratos/kratos/"}, }, { name: "https://go-kratos.dev/", args: args{"https", "go-kratos.dev/"}, want: &url.URL{Scheme: "https", Host: "go-kratos.dev/"}, }, { name: "https://www.google.com/", args: args{"https", "www.google.com/"}, want: &url.URL{Scheme: "https", Host: "www.google.com/"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewEndpoint(tt.args.scheme, tt.args.host); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewEndpoint() = %v, want %v", got, tt.want) } }) } } func TestParseEndpoint(t *testing.T) { type args struct { endpoints []string scheme string } tests := []struct { name string args args want string wantErr bool }{ { name: "kratos", args: args{endpoints: []string{"https://github.com/go-kratos/kratos"}, scheme: "https"}, want: "github.com", wantErr: false, }, { name: "test", args: args{endpoints: []string{"http://go-kratos.dev/"}, scheme: "https"}, want: "", wantErr: false, }, { name: "localhost:8080", args: args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpcs"}, want: "localhost:8080", wantErr: false, }, { name: "localhost:8081", args: args{endpoints: []string{"grpcs://localhost:8080/"}, scheme: "grpc"}, want: "", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseEndpoint(tt.args.endpoints, tt.args.scheme) if (err != nil) != tt.wantErr { t.Errorf("ParseEndpoint() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ParseEndpoint() got = %v, want %v", got, tt.want) } }) } } func TestSchema(t *testing.T) { tests := []struct { schema string secure bool want string }{ { schema: "http", secure: true, want: "https", }, { schema: "http", secure: false, want: "http", }, { schema: "grpc", secure: true, want: "grpcs", }, { schema: "grpc", secure: false, want: "grpc", }, } for _, tt := range tests { if got := Scheme(tt.schema, tt.secure); got != tt.want { t.Errorf("Schema() = %v, want %v", got, tt.want) } } } ================================================ FILE: internal/group/example_test.go ================================================ package group import "fmt" type Counter struct { Value int } func (c *Counter) Incr() { c.Value++ } func ExampleGroup_Get() { group := NewGroup(func() any { fmt.Println("Only Once") return &Counter{} }) // Create a new Counter group.Get("pass").(*Counter).Incr() // Get the created Counter again. group.Get("pass").(*Counter).Incr() // Output: // Only Once } func ExampleGroup_Reset() { group := NewGroup(func() any { return &Counter{} }) // Reset the new function and clear all created objects. group.Reset(func() any { fmt.Println("reset") return &Counter{} }) // Create a new Counter group.Get("pass").(*Counter).Incr() // Output:reset } ================================================ FILE: internal/group/group.go ================================================ // Package group provides a sample lazy load container. // The group only creating a new object not until the object is needed by user. // And it will cache all the objects to reduce the creation of object. package group import "sync" // Factory is a function that creates an object of type T. type Factory[T any] func() T // Group is a lazy load container. type Group[T any] struct { factory func() T vals map[string]T sync.RWMutex } // NewGroup news a group container. func NewGroup[T any](factory Factory[T]) *Group[T] { if factory == nil { panic("container.group: can't assign a nil to the new function") } return &Group[T]{ factory: factory, vals: make(map[string]T), } } // Get gets the object by the given key. func (g *Group[T]) Get(key string) T { g.RLock() v, ok := g.vals[key] if ok { g.RUnlock() return v } g.RUnlock() // slow path for group don`t have specified key value g.Lock() defer g.Unlock() v, ok = g.vals[key] if ok { return v } v = g.factory() g.vals[key] = v return v } // Reset resets the new function and deletes all existing objects. func (g *Group[T]) Reset(factory Factory[T]) { if factory == nil { panic("container.group: can't assign a nil to the new function") } g.Lock() g.factory = factory g.Unlock() g.Clear() } // Clear deletes all objects. func (g *Group[T]) Clear() { g.Lock() g.vals = make(map[string]T) g.Unlock() } ================================================ FILE: internal/group/group_test.go ================================================ package group import ( "reflect" "testing" ) func TestGroupGet(t *testing.T) { count := 0 g := NewGroup[int](func() int { count++ return count }) v := g.Get("key_0") if !reflect.DeepEqual(v, 1) { t.Errorf("expect 1, actual %v", v) } v = g.Get("key_1") if !reflect.DeepEqual(v, 2) { t.Errorf("expect 2, actual %v", v) } v = g.Get("key_0") if !reflect.DeepEqual(v, 1) { t.Errorf("expect 1, actual %v", v) } if !reflect.DeepEqual(count, 2) { t.Errorf("expect count 2, actual %v", count) } } func TestGroupReset(t *testing.T) { g := NewGroup(func() int { return 1 }) g.Get("key") call := false g.Reset(func() int { call = true return 1 }) length := 0 for range g.vals { length++ } if !reflect.DeepEqual(length, 0) { t.Errorf("expect length 0, actual %v", length) } g.Get("key") if !reflect.DeepEqual(call, true) { t.Errorf("expect call true, actual %v", call) } } func TestGroupClear(t *testing.T) { g := NewGroup(func() int { return 1 }) g.Get("key") length := 0 for range g.vals { length++ } if !reflect.DeepEqual(length, 1) { t.Errorf("expect length 1, actual %v", length) } g.Clear() length = 0 for range g.vals { length++ } if !reflect.DeepEqual(length, 0) { t.Errorf("expect length 0, actual %v", length) } } ================================================ FILE: internal/host/host.go ================================================ package host import ( "fmt" "net" "strconv" ) // ExtractHostPort from address func ExtractHostPort(addr string) (host string, port uint64, err error) { var ports string host, ports, err = net.SplitHostPort(addr) if err != nil { return } port, err = strconv.ParseUint(ports, 10, 16) //nolint:mnd return } func isValidIP(addr string) bool { ip := net.ParseIP(addr) return ip.IsGlobalUnicast() && !ip.IsInterfaceLocalMulticast() } // Port return a real port. func Port(lis net.Listener) (int, bool) { if addr, ok := lis.Addr().(*net.TCPAddr); ok { return addr.Port, true } return 0, false } // Extract returns a private addr and port. func Extract(hostPort string, lis net.Listener) (string, error) { addr, port, err := net.SplitHostPort(hostPort) if err != nil && lis == nil { return "", err } if lis != nil { p, ok := Port(lis) if !ok { return "", fmt.Errorf("failed to extract port: %v", lis.Addr()) } port = strconv.Itoa(p) } if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]" && addr != "::") { return net.JoinHostPort(addr, port), nil } ifaces, err := net.Interfaces() if err != nil { return "", err } var ( minIndex = 0 ips = make([]net.IP, 0, 1) ) for _, iface := range ifaces { if iface.Flags&net.FlagUp == 0 { continue } if iface.Index >= minIndex && len(ips) != 0 { continue } addrs, err := iface.Addrs() if err != nil { continue } for _, rawAddr := range addrs { var ip net.IP switch addr := rawAddr.(type) { case *net.IPAddr: ip = addr.IP case *net.IPNet: ip = addr.IP default: continue } if isValidIP(ip.String()) { minIndex = iface.Index ips = append(ips, ip) if ip.To4() != nil { break } } } } if len(ips) != 0 { return net.JoinHostPort(ips[len(ips)-1].String(), port), nil } return "", nil } ================================================ FILE: internal/host/host_test.go ================================================ package host import ( "net" "reflect" "testing" ) func TestValidIP(t *testing.T) { tests := []struct { addr string expect bool }{ {"127.0.0.1", false}, {"255.255.255.255", false}, {"0.0.0.0", false}, {"localhost", false}, {"10.1.0.1", true}, {"172.16.0.1", true}, {"192.168.1.1", true}, {"8.8.8.8", true}, {"1.1.1.1", true}, {"9.255.255.255", true}, {"10.0.0.0", true}, {"10.255.255.255", true}, {"11.0.0.0", true}, {"172.15.255.255", true}, {"172.16.0.0", true}, {"172.16.255.255", true}, {"172.23.18.255", true}, {"172.31.255.255", true}, {"172.31.0.0", true}, {"172.32.0.0", true}, {"192.167.255.255", true}, {"192.168.0.0", true}, {"192.168.255.255", true}, {"192.169.0.0", true}, {"fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, {"fc00::", true}, {"fcff:1200:0:44::", true}, {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, {"fe00::", true}, } for _, test := range tests { t.Run(test.addr, func(t *testing.T) { res := isValidIP(test.addr) if res != test.expect { t.Fatalf("expected %t got %t", test.expect, res) } }) } } func TestExtract(t *testing.T) { tests := []struct { addr string expect string }{ {"127.0.0.1:80", "127.0.0.1:80"}, {"10.0.0.1:80", "10.0.0.1:80"}, {"172.16.0.1:80", "172.16.0.1:80"}, {"192.168.1.1:80", "192.168.1.1:80"}, {"0.0.0.0:80", ""}, {"[::]:80", ""}, {":80", ""}, } for _, test := range tests { t.Run(test.addr, func(t *testing.T) { res, err := Extract(test.addr, nil) if err != nil { t.Fatal(err) } if res != test.expect && (test.expect == "" && test.addr == test.expect) { t.Fatalf("expected %s got %s", test.expect, res) } }) } lis, err := net.Listen("tcp", ":12345") if err != nil { t.Errorf("expected: %v got %v", nil, err) } res, err := Extract("", lis) if err != nil { t.Errorf("expected: %v got %v", nil, err) } expect, err := Extract(lis.Addr().String(), nil) if err != nil { t.Errorf("expected: %v got %v", nil, err) } if !reflect.DeepEqual(res, expect) { t.Errorf("expected %s got %s", expect, res) } } func TestExtract2(t *testing.T) { addr := "localhost:9001" lis, err := net.Listen("tcp", addr) if err != nil { t.Errorf("expected: %v got %v", nil, err) } res, err := Extract(addr, lis) if err != nil { t.Errorf("expected: %v got %v", nil, err) } if !reflect.DeepEqual(res, "localhost:9001") { t.Errorf("expected %s got %s", "localhost:9001", res) } } func TestPort(t *testing.T) { lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) } port, ok := Port(lis) if !ok || port == 0 { t.Fatalf("expected: %s got %d", lis.Addr().String(), port) } } func TestExtractHostPort(t *testing.T) { host, port, err := ExtractHostPort("127.0.0.1:8000") if err != nil { t.Fatalf("expected: %v got %v", nil, err) } t.Logf("host port: %s, %d", host, port) host, port, err = ExtractHostPort("www.bilibili.com:80") if err != nil { t.Fatalf("expected: %v got %v", nil, err) } t.Logf("host port: %s, %d", host, port) host, port, err = ExtractHostPort("consul://2/33") if err == nil { t.Fatalf("expected: not nil got %v", nil) } t.Logf("host port: %s, %d", host, port) } func TestIpIsUp(t *testing.T) { interfaces, err := net.Interfaces() if err != nil { t.Fail() } for i := range interfaces { println(interfaces[i].Name, interfaces[i].Flags&net.FlagUp) } } ================================================ FILE: internal/httputil/http.go ================================================ package httputil import ( "strings" ) const ( baseContentType = "application" ) // ContentType returns the content-type with base prefix. func ContentType(subtype string) string { return baseContentType + "/" + subtype } // ContentSubtype returns the content-subtype for the given content-type. The // given content-type must be a valid content-type that starts with // but no content-subtype will be returned. // according rfc7231. // contentType is assumed to be lowercase already. func ContentSubtype(contentType string) string { left := strings.Index(contentType, "/") if left == -1 { return "" } right := strings.Index(contentType, ";") if right == -1 { right = len(contentType) } if right < left { return "" } return contentType[left+1 : right] } ================================================ FILE: internal/httputil/http_test.go ================================================ package httputil import ( "testing" ) func TestContentSubtype(t *testing.T) { tests := []struct { contentType string want string }{ {"text/html; charset=utf-8", "html"}, {"multipart/form-data; boundary=something", "form-data"}, {"application/json; charset=utf-8", "json"}, {"application/json", "json"}, {"application/xml", "xml"}, {"text/xml", "xml"}, {";text/xml", ""}, {"application", ""}, } for _, test := range tests { t.Run(test.contentType, func(t *testing.T) { got := ContentSubtype(test.contentType) if got != test.want { t.Fatalf("want %v got %v", test.want, got) } }) } } func TestContentType(t *testing.T) { tests := []struct { name string subtype string want string }{ {"kratos", "kratos", "application/kratos"}, {"json", "json", "application/json"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ContentType(tt.subtype); got != tt.want { t.Errorf("ContentType() = %v, want %v", got, tt.want) } }) } } ================================================ FILE: internal/matcher/middleware.go ================================================ package matcher import ( "sort" "strings" "github.com/go-kratos/kratos/v2/middleware" ) // Matcher is a middleware matcher. type Matcher interface { Use(ms ...middleware.Middleware) Add(selector string, ms ...middleware.Middleware) Match(operation string) []middleware.Middleware } // New new a middleware matcher. func New() Matcher { return &matcher{ matches: make(map[string][]middleware.Middleware), } } type matcher struct { prefix []string defaults []middleware.Middleware matches map[string][]middleware.Middleware } func (m *matcher) Use(ms ...middleware.Middleware) { m.defaults = ms } func (m *matcher) Add(selector string, ms ...middleware.Middleware) { if strings.HasSuffix(selector, "*") { selector = strings.TrimSuffix(selector, "*") m.prefix = append(m.prefix, selector) // sort the prefix: // - /foo/bar // - /foo sort.Slice(m.prefix, func(i, j int) bool { return m.prefix[i] > m.prefix[j] }) } m.matches[selector] = ms } func (m *matcher) Match(operation string) []middleware.Middleware { ms := make([]middleware.Middleware, 0, len(m.defaults)) if len(m.defaults) > 0 { ms = append(ms, m.defaults...) } if next, ok := m.matches[operation]; ok { return append(ms, next...) } for _, prefix := range m.prefix { if strings.HasPrefix(operation, prefix) { return append(ms, m.matches[prefix]...) } } return ms } ================================================ FILE: internal/matcher/middleware_test.go ================================================ package matcher import ( "context" "testing" "github.com/go-kratos/kratos/v2/middleware" ) func logging(module string) middleware.Middleware { return func(middleware.Handler) middleware.Handler { return func(context.Context, any) (reply any, err error) { return module, nil } } } func equal(ms []middleware.Middleware, modules ...string) bool { if len(ms) == 0 { return false } for i, m := range ms { x, _ := m(nil)(nil, nil) if x != modules[i] { return false } } return true } func TestMatcher(t *testing.T) { m := New() m.Use(logging("logging")) m.Add("*", logging("*")) m.Add("/foo/*", logging("foo/*")) m.Add("/foo/bar/*", logging("foo/bar/*")) m.Add("/foo/bar", logging("foo/bar")) if ms := m.Match("/"); len(ms) != 2 { t.Fatal("not equal") } else if !equal(ms, "logging", "*") { t.Fatal("not equal") } if ms := m.Match("/foo/xxx"); len(ms) != 2 { t.Fatal("not equal") } else if !equal(ms, "logging", "foo/*") { t.Fatal("not equal") } if ms := m.Match("/foo/bar"); len(ms) != 2 { t.Fatal("not equal") } else if !equal(ms, "logging", "foo/bar") { t.Fatal("not equal") } if ms := m.Match("/foo/bar/x"); len(ms) != 2 { t.Fatal("not equal") } else if !equal(ms, "logging", "foo/bar/*") { t.Fatal("not equal") } } ================================================ FILE: internal/testdata/binding/generate.go ================================================ package binding //go:generate protoc -I . --go_out=paths=source_relative:. ./test.proto ================================================ FILE: internal/testdata/binding/test.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.0 // protoc v3.17.3 // source: test.proto package binding import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" 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) ) // The request message containing the user's name. type HelloRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Sub *Sub `protobuf:"bytes,2,opt,name=sub,proto3" json:"sub,omitempty"` UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"` OptInt32 *int32 `protobuf:"varint,4,opt,name=opt_int32,json=optInt32,proto3,oneof" json:"opt_int32,omitempty"` OptInt64 *int64 `protobuf:"varint,5,opt,name=opt_int64,json=optInt64,proto3,oneof" json:"opt_int64,omitempty"` OptString *string `protobuf:"bytes,6,opt,name=opt_string,json=optString,proto3,oneof" json:"opt_string,omitempty"` SubField *Sub `protobuf:"bytes,7,opt,name=subField,proto3" json:"subField,omitempty"` TestRepeated []string `protobuf:"bytes,8,rep,name=test_repeated,proto3" json:"test_repeated,omitempty"` } func (x *HelloRequest) Reset() { *x = HelloRequest{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloRequest) ProtoMessage() {} func (x *HelloRequest) ProtoReflect() protoreflect.Message { mi := &file_test_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 HelloRequest.ProtoReflect.Descriptor instead. func (*HelloRequest) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } func (x *HelloRequest) GetName() string { if x != nil { return x.Name } return "" } func (x *HelloRequest) GetSub() *Sub { if x != nil { return x.Sub } return nil } func (x *HelloRequest) GetUpdateMask() *fieldmaskpb.FieldMask { if x != nil { return x.UpdateMask } return nil } func (x *HelloRequest) GetOptInt32() int32 { if x != nil && x.OptInt32 != nil { return *x.OptInt32 } return 0 } func (x *HelloRequest) GetOptInt64() int64 { if x != nil && x.OptInt64 != nil { return *x.OptInt64 } return 0 } func (x *HelloRequest) GetOptString() string { if x != nil && x.OptString != nil { return *x.OptString } return "" } func (x *HelloRequest) GetSubField() *Sub { if x != nil { return x.SubField } return nil } func (x *HelloRequest) GetTestRepeated() []string { if x != nil { return x.TestRepeated } return nil } type Sub struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,json=naming,proto3" json:"name,omitempty"` } func (x *Sub) Reset() { *x = Sub{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Sub) String() string { return protoimpl.X.MessageStringOf(x) } func (*Sub) ProtoMessage() {} func (x *Sub) ProtoReflect() protoreflect.Message { mi := &file_test_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 Sub.ProtoReflect.Descriptor instead. func (*Sub) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{1} } func (x *Sub) GetName() string { if x != nil { return x.Name } return "" } var File_test_proto protoreflect.FileDescriptor var file_test_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe2, 0x02, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 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, 0x12, 0x1e, 0x0a, 0x03, 0x73, 0x75, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x75, 0x62, 0x52, 0x03, 0x73, 0x75, 0x62, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x73, 0x6b, 0x12, 0x20, 0x0a, 0x09, 0x6f, 0x70, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x6f, 0x70, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x6f, 0x70, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, 0x6f, 0x70, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x75, 0x62, 0x52, 0x08, 0x73, 0x75, 0x62, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6f, 0x70, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6f, 0x70, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x6f, 0x70, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x1b, 0x0a, 0x03, 0x53, 0x75, 0x62, 0x12, 0x14, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x42, 0x2f, 0x5a, 0x2d, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_test_proto_rawDescOnce sync.Once file_test_proto_rawDescData = file_test_proto_rawDesc ) func file_test_proto_rawDescGZIP() []byte { file_test_proto_rawDescOnce.Do(func() { file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData) }) return file_test_proto_rawDescData } var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_test_proto_goTypes = []interface{}{ (*HelloRequest)(nil), // 0: binding.HelloRequest (*Sub)(nil), // 1: binding.Sub (*fieldmaskpb.FieldMask)(nil), // 2: google.protobuf.FieldMask } var file_test_proto_depIdxs = []int32{ 1, // 0: binding.HelloRequest.sub:type_name -> binding.Sub 2, // 1: binding.HelloRequest.update_mask:type_name -> google.protobuf.FieldMask 1, // 2: binding.HelloRequest.subField:type_name -> binding.Sub 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 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_test_proto_init() } func file_test_proto_init() { if File_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Sub); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_test_proto_msgTypes[0].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_test_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_test_proto_goTypes, DependencyIndexes: file_test_proto_depIdxs, MessageInfos: file_test_proto_msgTypes, }.Build() File_test_proto = out.File file_test_proto_rawDesc = nil file_test_proto_goTypes = nil file_test_proto_depIdxs = nil } ================================================ FILE: internal/testdata/binding/test.proto ================================================ syntax = "proto3"; package binding; import "google/protobuf/field_mask.proto"; option go_package = "github.com/go-kratos/kratos/transport/binding"; // The request message containing the user's name. message HelloRequest { string name = 1; Sub sub = 2; google.protobuf.FieldMask update_mask = 3; optional int32 opt_int32 = 4; optional int64 opt_int64 = 5; optional string opt_string = 6; Sub subField = 7; repeated string test_repeated = 8 [json_name = "test_repeated"]; } message Sub{ string name = 1 [json_name = "naming"]; } ================================================ FILE: internal/testdata/complex/complex.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 // protoc v5.29.3 // source: complex.proto package complex import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" 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 Sex int32 const ( Sex_man Sex = 0 Sex_woman Sex = 1 ) // Enum value maps for Sex. var ( Sex_name = map[int32]string{ 0: "man", 1: "woman", } Sex_value = map[string]int32{ "man": 0, "woman": 1, } ) func (x Sex) Enum() *Sex { p := new(Sex) *p = x return p } func (x Sex) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Sex) Descriptor() protoreflect.EnumDescriptor { return file_complex_proto_enumTypes[0].Descriptor() } func (Sex) Type() protoreflect.EnumType { return &file_complex_proto_enumTypes[0] } func (x Sex) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Sex.Descriptor instead. func (Sex) EnumDescriptor() ([]byte, []int) { return file_complex_proto_rawDescGZIP(), []int{0} } // SimpleMessage represents a simple message sent to the Echo service. type Complex struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Id represents the message identifier. Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` NoOne string `protobuf:"bytes,2,opt,name=no_one,json=numberOne,proto3" json:"no_one,omitempty"` Simple *Simple `protobuf:"bytes,3,opt,name=simple,json=very_simple,proto3" json:"simple,omitempty"` Strings []string `protobuf:"bytes,4,rep,name=strings,proto3" json:"strings,omitempty"` Simples []*Simple `protobuf:"bytes,27,rep,name=simples,proto3" json:"simples,omitempty"` B bool `protobuf:"varint,5,opt,name=b,proto3" json:"b,omitempty"` Sex Sex `protobuf:"varint,6,opt,name=sex,proto3,enum=testproto.Sex" json:"sex,omitempty"` Age int32 `protobuf:"varint,7,opt,name=age,proto3" json:"age,omitempty"` A uint32 `protobuf:"varint,8,opt,name=a,proto3" json:"a,omitempty"` Count uint64 `protobuf:"varint,9,opt,name=count,proto3" json:"count,omitempty"` Price float32 `protobuf:"fixed32,10,opt,name=price,proto3" json:"price,omitempty"` D float64 `protobuf:"fixed64,11,opt,name=d,proto3" json:"d,omitempty"` Byte []byte `protobuf:"bytes,12,opt,name=byte,proto3" json:"byte,omitempty"` Timestamp *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Duration *durationpb.Duration `protobuf:"bytes,14,opt,name=duration,proto3" json:"duration,omitempty"` Field *fieldmaskpb.FieldMask `protobuf:"bytes,15,opt,name=field,proto3" json:"field,omitempty"` Double *wrapperspb.DoubleValue `protobuf:"bytes,16,opt,name=double,proto3" json:"double,omitempty"` Float *wrapperspb.FloatValue `protobuf:"bytes,17,opt,name=float,proto3" json:"float,omitempty"` Int64 *wrapperspb.Int64Value `protobuf:"bytes,18,opt,name=int64,proto3" json:"int64,omitempty"` Int32 *wrapperspb.Int32Value `protobuf:"bytes,19,opt,name=int32,proto3" json:"int32,omitempty"` Uint64 *wrapperspb.UInt64Value `protobuf:"bytes,20,opt,name=uint64,proto3" json:"uint64,omitempty"` Uint32 *wrapperspb.UInt32Value `protobuf:"bytes,21,opt,name=uint32,proto3" json:"uint32,omitempty"` Bool *wrapperspb.BoolValue `protobuf:"bytes,22,opt,name=bool,proto3" json:"bool,omitempty"` String_ *wrapperspb.StringValue `protobuf:"bytes,23,opt,name=string,proto3" json:"string,omitempty"` Bytes *wrapperspb.BytesValue `protobuf:"bytes,24,opt,name=bytes,proto3" json:"bytes,omitempty"` Map map[string]string `protobuf:"bytes,25,rep,name=map,proto3" json:"map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` MapInt64Key map[int64]string `protobuf:"bytes,26,rep,name=map_int64_key,proto3" json:"map_int64_key,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *Complex) Reset() { *x = Complex{} if protoimpl.UnsafeEnabled { mi := &file_complex_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Complex) String() string { return protoimpl.X.MessageStringOf(x) } func (*Complex) ProtoMessage() {} func (x *Complex) ProtoReflect() protoreflect.Message { mi := &file_complex_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 Complex.ProtoReflect.Descriptor instead. func (*Complex) Descriptor() ([]byte, []int) { return file_complex_proto_rawDescGZIP(), []int{0} } func (x *Complex) GetId() int64 { if x != nil { return x.Id } return 0 } func (x *Complex) GetNoOne() string { if x != nil { return x.NoOne } return "" } func (x *Complex) GetSimple() *Simple { if x != nil { return x.Simple } return nil } func (x *Complex) GetStrings() []string { if x != nil { return x.Strings } return nil } func (x *Complex) GetSimples() []*Simple { if x != nil { return x.Simples } return nil } func (x *Complex) GetB() bool { if x != nil { return x.B } return false } func (x *Complex) GetSex() Sex { if x != nil { return x.Sex } return Sex_man } func (x *Complex) GetAge() int32 { if x != nil { return x.Age } return 0 } func (x *Complex) GetA() uint32 { if x != nil { return x.A } return 0 } func (x *Complex) GetCount() uint64 { if x != nil { return x.Count } return 0 } func (x *Complex) GetPrice() float32 { if x != nil { return x.Price } return 0 } func (x *Complex) GetD() float64 { if x != nil { return x.D } return 0 } func (x *Complex) GetByte() []byte { if x != nil { return x.Byte } return nil } func (x *Complex) GetTimestamp() *timestamppb.Timestamp { if x != nil { return x.Timestamp } return nil } func (x *Complex) GetDuration() *durationpb.Duration { if x != nil { return x.Duration } return nil } func (x *Complex) GetField() *fieldmaskpb.FieldMask { if x != nil { return x.Field } return nil } func (x *Complex) GetDouble() *wrapperspb.DoubleValue { if x != nil { return x.Double } return nil } func (x *Complex) GetFloat() *wrapperspb.FloatValue { if x != nil { return x.Float } return nil } func (x *Complex) GetInt64() *wrapperspb.Int64Value { if x != nil { return x.Int64 } return nil } func (x *Complex) GetInt32() *wrapperspb.Int32Value { if x != nil { return x.Int32 } return nil } func (x *Complex) GetUint64() *wrapperspb.UInt64Value { if x != nil { return x.Uint64 } return nil } func (x *Complex) GetUint32() *wrapperspb.UInt32Value { if x != nil { return x.Uint32 } return nil } func (x *Complex) GetBool() *wrapperspb.BoolValue { if x != nil { return x.Bool } return nil } func (x *Complex) GetString_() *wrapperspb.StringValue { if x != nil { return x.String_ } return nil } func (x *Complex) GetBytes() *wrapperspb.BytesValue { if x != nil { return x.Bytes } return nil } func (x *Complex) GetMap() map[string]string { if x != nil { return x.Map } return nil } func (x *Complex) GetMapInt64Key() map[int64]string { if x != nil { return x.MapInt64Key } return nil } type Simple struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Component string `protobuf:"bytes,1,opt,name=component,proto3" json:"component,omitempty"` } func (x *Simple) Reset() { *x = Simple{} if protoimpl.UnsafeEnabled { mi := &file_complex_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Simple) String() string { return protoimpl.X.MessageStringOf(x) } func (*Simple) ProtoMessage() {} func (x *Simple) ProtoReflect() protoreflect.Message { mi := &file_complex_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 Simple.ProtoReflect.Descriptor instead. func (*Simple) Descriptor() ([]byte, []int) { return file_complex_proto_rawDescGZIP(), []int{1} } func (x *Simple) GetComponent() string { if x != nil { return x.Component } return "" } var File_complex_proto protoreflect.FileDescriptor var file_complex_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x09, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x06, 0x6e, 0x6f, 0x5f, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x4f, 0x6e, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2b, 0x0a, 0x07, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x18, 0x1b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x07, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x01, 0x62, 0x12, 0x20, 0x0a, 0x03, 0x73, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x78, 0x52, 0x03, 0x73, 0x65, 0x78, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x01, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x79, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x79, 0x74, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x34, 0x0a, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x31, 0x0a, 0x05, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x34, 0x0a, 0x06, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x34, 0x0a, 0x06, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x12, 0x2e, 0x0a, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x2e, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x6d, 0x61, 0x70, 0x12, 0x49, 0x0a, 0x0d, 0x6d, 0x61, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x1a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x2e, 0x4d, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x61, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x6b, 0x65, 0x79, 0x1a, 0x36, 0x0a, 0x08, 0x4d, 0x61, 0x70, 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, 0x1a, 0x3e, 0x0a, 0x10, 0x4d, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4b, 0x65, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 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, 0x22, 0x26, 0x0a, 0x06, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x2a, 0x19, 0x0a, 0x03, 0x73, 0x65, 0x78, 0x12, 0x07, 0x0a, 0x03, 0x6d, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x77, 0x6f, 0x6d, 0x61, 0x6e, 0x10, 0x01, 0x42, 0x57, 0x5a, 0x55, 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, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x3b, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_complex_proto_rawDescOnce sync.Once file_complex_proto_rawDescData = file_complex_proto_rawDesc ) func file_complex_proto_rawDescGZIP() []byte { file_complex_proto_rawDescOnce.Do(func() { file_complex_proto_rawDescData = protoimpl.X.CompressGZIP(file_complex_proto_rawDescData) }) return file_complex_proto_rawDescData } var file_complex_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_complex_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_complex_proto_goTypes = []interface{}{ (Sex)(0), // 0: testproto.sex (*Complex)(nil), // 1: testproto.Complex (*Simple)(nil), // 2: testproto.Simple nil, // 3: testproto.Complex.MapEntry nil, // 4: testproto.Complex.MapInt64KeyEntry (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 6: google.protobuf.Duration (*fieldmaskpb.FieldMask)(nil), // 7: google.protobuf.FieldMask (*wrapperspb.DoubleValue)(nil), // 8: google.protobuf.DoubleValue (*wrapperspb.FloatValue)(nil), // 9: google.protobuf.FloatValue (*wrapperspb.Int64Value)(nil), // 10: google.protobuf.Int64Value (*wrapperspb.Int32Value)(nil), // 11: google.protobuf.Int32Value (*wrapperspb.UInt64Value)(nil), // 12: google.protobuf.UInt64Value (*wrapperspb.UInt32Value)(nil), // 13: google.protobuf.UInt32Value (*wrapperspb.BoolValue)(nil), // 14: google.protobuf.BoolValue (*wrapperspb.StringValue)(nil), // 15: google.protobuf.StringValue (*wrapperspb.BytesValue)(nil), // 16: google.protobuf.BytesValue } var file_complex_proto_depIdxs = []int32{ 2, // 0: testproto.Complex.simple:type_name -> testproto.Simple 2, // 1: testproto.Complex.simples:type_name -> testproto.Simple 0, // 2: testproto.Complex.sex:type_name -> testproto.sex 5, // 3: testproto.Complex.timestamp:type_name -> google.protobuf.Timestamp 6, // 4: testproto.Complex.duration:type_name -> google.protobuf.Duration 7, // 5: testproto.Complex.field:type_name -> google.protobuf.FieldMask 8, // 6: testproto.Complex.double:type_name -> google.protobuf.DoubleValue 9, // 7: testproto.Complex.float:type_name -> google.protobuf.FloatValue 10, // 8: testproto.Complex.int64:type_name -> google.protobuf.Int64Value 11, // 9: testproto.Complex.int32:type_name -> google.protobuf.Int32Value 12, // 10: testproto.Complex.uint64:type_name -> google.protobuf.UInt64Value 13, // 11: testproto.Complex.uint32:type_name -> google.protobuf.UInt32Value 14, // 12: testproto.Complex.bool:type_name -> google.protobuf.BoolValue 15, // 13: testproto.Complex.string:type_name -> google.protobuf.StringValue 16, // 14: testproto.Complex.bytes:type_name -> google.protobuf.BytesValue 3, // 15: testproto.Complex.map:type_name -> testproto.Complex.MapEntry 4, // 16: testproto.Complex.map_int64_key:type_name -> testproto.Complex.MapInt64KeyEntry 17, // [17:17] is the sub-list for method output_type 17, // [17:17] is the sub-list for method input_type 17, // [17:17] is the sub-list for extension type_name 17, // [17:17] is the sub-list for extension extendee 0, // [0:17] is the sub-list for field type_name } func init() { file_complex_proto_init() } func file_complex_proto_init() { if File_complex_proto != nil { return } if !protoimpl.UnsafeEnabled { file_complex_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Complex); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_complex_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Simple); 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_complex_proto_rawDesc, NumEnums: 1, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_complex_proto_goTypes, DependencyIndexes: file_complex_proto_depIdxs, EnumInfos: file_complex_proto_enumTypes, MessageInfos: file_complex_proto_msgTypes, }.Build() File_complex_proto = out.File file_complex_proto_rawDesc = nil file_complex_proto_goTypes = nil file_complex_proto_depIdxs = nil } ================================================ FILE: internal/testdata/complex/complex.proto ================================================ syntax = "proto3"; option go_package = "github.com/go-kratos/kratos/cmd/protoc-gen-go-http/internal/encoding/complex/;complex"; package testproto; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/field_mask.proto"; import "google/protobuf/wrappers.proto"; // SimpleMessage represents a simple message sent to the Echo service. message Complex { // Id represents the message identifier. int64 id = 1; string no_one = 2 [json_name = "numberOne"]; Simple simple = 3 [json_name = "very_simple"]; repeated string strings = 4; repeated Simple simples = 27; bool b = 5; sex sex = 6; int32 age = 7; uint32 a = 8; uint64 count = 9; float price = 10; double d = 11; bytes byte = 12; google.protobuf.Timestamp timestamp = 13; google.protobuf.Duration duration = 14; google.protobuf.FieldMask field = 15; google.protobuf.DoubleValue double = 16; google.protobuf.FloatValue float = 17; google.protobuf.Int64Value int64 = 18; google.protobuf.Int32Value int32 = 19; google.protobuf.UInt64Value uint64 = 20; google.protobuf.UInt32Value uint32 = 21; google.protobuf.BoolValue bool = 22; google.protobuf.StringValue string = 23; google.protobuf.BytesValue bytes = 24; map map = 25; map map_int64_key = 26 [json_name = "map_int64_key"]; } message Simple { string component = 1; } enum sex { man = 0; woman = 1; } ================================================ FILE: internal/testdata/complex/generate.go ================================================ package complex //go:generate protoc -I . --go_out=paths=source_relative:. ./complex.proto ================================================ FILE: internal/testdata/encoding/test.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 // protoc v3.19.4 // source: encoding/test.proto package encoding import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" structpb "google.golang.org/protobuf/types/known/structpb" 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 TestModel struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Hobby []string `protobuf:"bytes,3,rep,name=hobby,proto3" json:"hobby,omitempty"` Attrs map[string]string `protobuf:"bytes,4,rep,name=attrs,proto3" json:"attrs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *TestModel) Reset() { *x = TestModel{} if protoimpl.UnsafeEnabled { mi := &file_encoding_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestModel) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestModel) ProtoMessage() {} func (x *TestModel) ProtoReflect() protoreflect.Message { mi := &file_encoding_test_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 TestModel.ProtoReflect.Descriptor instead. func (*TestModel) Descriptor() ([]byte, []int) { return file_encoding_test_proto_rawDescGZIP(), []int{0} } func (x *TestModel) GetId() int64 { if x != nil { return x.Id } return 0 } func (x *TestModel) GetName() string { if x != nil { return x.Name } return "" } func (x *TestModel) GetHobby() []string { if x != nil { return x.Hobby } return nil } func (x *TestModel) GetAttrs() map[string]string { if x != nil { return x.Attrs } return nil } type StructPb struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Data *structpb.Struct `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` DataList []*structpb.Struct `protobuf:"bytes,2,rep,name=data_list,json=dataList,proto3" json:"data_list,omitempty"` } func (x *StructPb) Reset() { *x = StructPb{} if protoimpl.UnsafeEnabled { mi := &file_encoding_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StructPb) String() string { return protoimpl.X.MessageStringOf(x) } func (*StructPb) ProtoMessage() {} func (x *StructPb) ProtoReflect() protoreflect.Message { mi := &file_encoding_test_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 StructPb.ProtoReflect.Descriptor instead. func (*StructPb) Descriptor() ([]byte, []int) { return file_encoding_test_proto_rawDescGZIP(), []int{1} } func (x *StructPb) GetData() *structpb.Struct { if x != nil { return x.Data } return nil } func (x *StructPb) GetDataList() []*structpb.Struct { if x != nil { return x.DataList } return nil } var File_encoding_test_proto protoreflect.FileDescriptor var file_encoding_test_proto_rawDesc = []byte{ 0x0a, 0x13, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x01, 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x68, 0x6f, 0x62, 0x62, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x68, 0x6f, 0x62, 0x62, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x1a, 0x38, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x72, 0x73, 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, 0x22, 0x6d, 0x0a, 0x08, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x50, 0x62, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x34, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2e, 0x2f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_encoding_test_proto_rawDescOnce sync.Once file_encoding_test_proto_rawDescData = file_encoding_test_proto_rawDesc ) func file_encoding_test_proto_rawDescGZIP() []byte { file_encoding_test_proto_rawDescOnce.Do(func() { file_encoding_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_encoding_test_proto_rawDescData) }) return file_encoding_test_proto_rawDescData } var file_encoding_test_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_encoding_test_proto_goTypes = []interface{}{ (*TestModel)(nil), // 0: test.test_model (*StructPb)(nil), // 1: test.StructPb nil, // 2: test.test_model.AttrsEntry (*structpb.Struct)(nil), // 3: google.protobuf.Struct } var file_encoding_test_proto_depIdxs = []int32{ 2, // 0: test.test_model.attrs:type_name -> test.test_model.AttrsEntry 3, // 1: test.StructPb.data:type_name -> google.protobuf.Struct 3, // 2: test.StructPb.data_list:type_name -> google.protobuf.Struct 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 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_encoding_test_proto_init() } func file_encoding_test_proto_init() { if File_encoding_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_encoding_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestModel); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_encoding_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StructPb); 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_encoding_test_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 0, }, GoTypes: file_encoding_test_proto_goTypes, DependencyIndexes: file_encoding_test_proto_depIdxs, MessageInfos: file_encoding_test_proto_msgTypes, }.Build() File_encoding_test_proto = out.File file_encoding_test_proto_rawDesc = nil file_encoding_test_proto_goTypes = nil file_encoding_test_proto_depIdxs = nil } ================================================ FILE: internal/testdata/encoding/test.proto ================================================ syntax = "proto3"; package test; option go_package = "../encoding"; import "google/protobuf/struct.proto"; message test_model { int64 id = 1; string name = 2; repeated string hobby = 3; map attrs = 4; } message StructPb { google.protobuf.Struct data = 1; repeated google.protobuf.Struct data_list = 2; } ================================================ FILE: internal/testdata/helloworld/generate.go ================================================ package helloworld //go:generate protoc -I . -I ../../../third_party --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. --go-http_out=paths=source_relative:. ./helloworld.proto ================================================ FILE: internal/testdata/helloworld/helloworld.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.0 // protoc v3.17.3 // source: helloworld.proto package helloworld import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" 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) ) // The request message containing the user's name. type HelloRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } func (x *HelloRequest) Reset() { *x = HelloRequest{} if protoimpl.UnsafeEnabled { mi := &file_helloworld_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloRequest) ProtoMessage() {} func (x *HelloRequest) ProtoReflect() protoreflect.Message { mi := &file_helloworld_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 HelloRequest.ProtoReflect.Descriptor instead. func (*HelloRequest) Descriptor() ([]byte, []int) { return file_helloworld_proto_rawDescGZIP(), []int{0} } func (x *HelloRequest) GetName() string { if x != nil { return x.Name } return "" } // The response message containing the greetings type HelloReply struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` } func (x *HelloReply) Reset() { *x = HelloReply{} if protoimpl.UnsafeEnabled { mi := &file_helloworld_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *HelloReply) String() string { return protoimpl.X.MessageStringOf(x) } func (*HelloReply) ProtoMessage() {} func (x *HelloReply) ProtoReflect() protoreflect.Message { mi := &file_helloworld_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 HelloReply.ProtoReflect.Descriptor instead. func (*HelloReply) Descriptor() ([]byte, []int) { return file_helloworld_proto_rawDescGZIP(), []int{1} } func (x *HelloReply) GetMessage() string { if x != nil { return x.Message } return "" } var File_helloworld_proto protoreflect.FileDescriptor var file_helloworld_proto_rawDesc = []byte{ 0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 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, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 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, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0xab, 0x01, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x58, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x46, 0x0a, 0x0e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x28, 0x01, 0x30, 0x01, 0x42, 0x3d, 0x5a, 0x3b, 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, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_helloworld_proto_rawDescOnce sync.Once file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc ) func file_helloworld_proto_rawDescGZIP() []byte { file_helloworld_proto_rawDescOnce.Do(func() { file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData) }) return file_helloworld_proto_rawDescData } var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_helloworld_proto_goTypes = []interface{}{ (*HelloRequest)(nil), // 0: helloworld.HelloRequest (*HelloReply)(nil), // 1: helloworld.HelloReply } var file_helloworld_proto_depIdxs = []int32{ 0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest 0, // 1: helloworld.Greeter.SayHelloStream:input_type -> helloworld.HelloRequest 1, // 2: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply 1, // 3: helloworld.Greeter.SayHelloStream:output_type -> helloworld.HelloReply 2, // [2:4] is the sub-list for method output_type 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_helloworld_proto_init() } func file_helloworld_proto_init() { if File_helloworld_proto != nil { return } if !protoimpl.UnsafeEnabled { file_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelloReply); 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_helloworld_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_helloworld_proto_goTypes, DependencyIndexes: file_helloworld_proto_depIdxs, MessageInfos: file_helloworld_proto_msgTypes, }.Build() File_helloworld_proto = out.File file_helloworld_proto_rawDesc = nil file_helloworld_proto_goTypes = nil file_helloworld_proto_depIdxs = nil } ================================================ FILE: internal/testdata/helloworld/helloworld.proto ================================================ syntax = "proto3"; package helloworld; import "google/api/annotations.proto"; option go_package = "github.com/go-kratos/kratos/v2/internal/testdata/helloworld"; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) { option (google.api.http) = { get: "/helloworld/{name}", }; } // Sends a greeting rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply); } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; } ================================================ FILE: internal/testdata/helloworld/helloworld_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 // - protoc v3.17.3 // source: helloworld.proto package helloworld 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 // GreeterClient is the client API for Greeter 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 GreeterClient interface { // Sends a greeting SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) // Sends a greeting SayHelloStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error) } type greeterClient struct { cc grpc.ClientConnInterface } func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { return &greeterClient{cc} } func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { out := new(HelloReply) err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *greeterClient) SayHelloStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error) { stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], "/helloworld.Greeter/SayHelloStream", opts...) if err != nil { return nil, err } x := &greeterSayHelloStreamClient{stream} return x, nil } type Greeter_SayHelloStreamClient interface { Send(*HelloRequest) error Recv() (*HelloReply, error) grpc.ClientStream } type greeterSayHelloStreamClient struct { grpc.ClientStream } func (x *greeterSayHelloStreamClient) Send(m *HelloRequest) error { return x.ClientStream.SendMsg(m) } func (x *greeterSayHelloStreamClient) Recv() (*HelloReply, error) { m := new(HelloReply) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // GreeterServer is the server API for Greeter service. // All implementations must embed UnimplementedGreeterServer // for forward compatibility type GreeterServer interface { // Sends a greeting SayHello(context.Context, *HelloRequest) (*HelloReply, error) // Sends a greeting SayHelloStream(Greeter_SayHelloStreamServer) error mustEmbedUnimplementedGreeterServer() } // UnimplementedGreeterServer must be embedded to have forward compatible implementations. type UnimplementedGreeterServer struct { } func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") } func (UnimplementedGreeterServer) SayHelloStream(Greeter_SayHelloStreamServer) error { return status.Errorf(codes.Unimplemented, "method SayHelloStream not implemented") } func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} // UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to GreeterServer will // result in compilation errors. type UnsafeGreeterServer interface { mustEmbedUnimplementedGreeterServer() } func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { s.RegisterService(&Greeter_ServiceDesc, srv) } func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GreeterServer).SayHello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/helloworld.Greeter/SayHello", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } func _Greeter_SayHelloStream_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(GreeterServer).SayHelloStream(&greeterSayHelloStreamServer{stream}) } type Greeter_SayHelloStreamServer interface { Send(*HelloReply) error Recv() (*HelloRequest, error) grpc.ServerStream } type greeterSayHelloStreamServer struct { grpc.ServerStream } func (x *greeterSayHelloStreamServer) Send(m *HelloReply) error { return x.ServerStream.SendMsg(m) } func (x *greeterSayHelloStreamServer) Recv() (*HelloRequest, error) { m := new(HelloRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Greeter_ServiceDesc = grpc.ServiceDesc{ ServiceName: "helloworld.Greeter", HandlerType: (*GreeterServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SayHello", Handler: _Greeter_SayHello_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SayHelloStream", Handler: _Greeter_SayHelloStream_Handler, ServerStreams: true, ClientStreams: true, }, }, Metadata: "helloworld.proto", } ================================================ FILE: internal/testdata/helloworld/helloworld_http.pb.go ================================================ // Code generated by protoc-gen-go-http. DO NOT EDIT. // versions: // protoc-gen-go-http v2.3.1 package helloworld 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 const OperationGreeterSayHello = "/helloworld.Greeter/SayHello" type GreeterHTTPServer interface { SayHello(context.Context, *HelloRequest) (*HelloReply, error) } func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) { r := s.Route("/") r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv)) } func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error { return func(ctx http.Context) error { var in HelloRequest if err := ctx.BindQuery(&in); err != nil { return err } if err := ctx.BindVars(&in); err != nil { return err } http.SetOperation(ctx, OperationGreeterSayHello) h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { return srv.SayHello(ctx, req.(*HelloRequest)) }) out, err := h(ctx, &in) if err != nil { return err } reply := out.(*HelloReply) return ctx.Result(200, reply) } } type GreeterHTTPClient interface { SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error) } type GreeterHTTPClientImpl struct { cc *http.Client } func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient { return &GreeterHTTPClientImpl{client} } func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) { var out HelloReply pattern := "/helloworld/{name}" path := binding.EncodeURL(pattern, in, true) opts = append(opts, http.Operation(OperationGreeterSayHello)) 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 } ================================================ FILE: log/README.md ================================================ # Logger ## Usage ### Structured logging ```go logger := log.NewStdLogger(os.Stdout) // fields & valuer logger = log.With(logger, "service.name", "helloworld", "service.version", "v1.0.0", "ts", log.DefaultTimestamp, "caller", log.DefaultCaller, ) logger.Log(log.LevelInfo, "key", "value") // helper helper := log.NewHelper(logger) helper.Log(log.LevelInfo, "key", "value") helper.Info("info message") helper.Infof("info %s", "message") helper.Infow("key", "value") // filter log := log.NewHelper(log.NewFilter(logger, log.FilterLevel(log.LevelInfo), log.FilterKey("foo"), log.FilterValue("bar"), log.FilterFunc(customFilter), )) log.Debug("debug log") log.Info("info log") log.Warn("warn log") log.Error("warn log") ``` ## Third party log library ### zap ```shell go get -u github.com/go-kratos/kratos/contrib/log/zap/v2 ``` ### logrus ```shell go get -u github.com/go-kratos/kratos/contrib/log/logrus/v2 ``` ### fluent ```shell go get -u github.com/go-kratos/kratos/contrib/log/fluent/v2 ``` ### aliyun ```shell go get -u github.com/go-kratos/kratos/contrib/log/aliyun/v2 ``` ================================================ FILE: log/filter.go ================================================ package log // FilterOption is filter option. type FilterOption func(*Filter) const fuzzyStr = "***" // FilterLevel with filter level. func FilterLevel(level Level) FilterOption { return func(opts *Filter) { opts.level = level } } // FilterKey with filter key. func FilterKey(key ...string) FilterOption { return func(o *Filter) { for _, v := range key { o.key[v] = struct{}{} } } } // FilterValue with filter value. func FilterValue(value ...string) FilterOption { return func(o *Filter) { for _, v := range value { o.value[v] = struct{}{} } } } // FilterFunc with filter func. func FilterFunc(f func(level Level, keyvals ...any) bool) FilterOption { return func(o *Filter) { o.filter = f } } // Filter is a logger filter. type Filter struct { logger Logger level Level key map[any]struct{} value map[any]struct{} filter func(level Level, keyvals ...any) bool } // NewFilter new a logger filter. func NewFilter(logger Logger, opts ...FilterOption) *Filter { options := Filter{ logger: logger, key: make(map[any]struct{}), value: make(map[any]struct{}), } for _, o := range opts { o(&options) } return &options } // Log Print log by level and keyvals. func (f *Filter) Log(level Level, keyvals ...any) error { if level < f.level { return nil } // prefixkv contains the slice of arguments defined as prefixes during the log initialization var prefixkv []any l, ok := f.logger.(*logger) if ok && len(l.prefix) > 0 { prefixkv = make([]any, 0, len(l.prefix)) prefixkv = append(prefixkv, l.prefix...) } if f.filter != nil && (f.filter(level, prefixkv...) || f.filter(level, keyvals...)) { return nil } if len(f.key) > 0 || len(f.value) > 0 { for i := 0; i < len(keyvals); i += 2 { v := i + 1 if v >= len(keyvals) { break } if _, ok := f.key[keyvals[i]]; ok { keyvals[v] = fuzzyStr } if _, ok := f.value[keyvals[v]]; ok { keyvals[v] = fuzzyStr } } } return f.logger.Log(level, keyvals...) } ================================================ FILE: log/filter_test.go ================================================ package log import ( "bytes" "context" "io" "strings" "sync" "testing" "time" ) func TestFilterAll(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewHelper(NewFilter(logger, FilterLevel(LevelDebug), FilterKey("username"), FilterValue("hello"), FilterFunc(testFilterFunc), )) log.Log(LevelDebug, "msg", "test debug") log.Info("hello") log.Infow("password", "123456") log.Infow("username", "kratos") log.Warn("warn log") } func TestFilterLevel(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewHelper(NewFilter(NewFilter(logger, FilterLevel(LevelWarn)))) log.Log(LevelDebug, "msg1", "te1st debug") log.Debug("test debug") log.Debugf("test %s", "debug") log.Debugw("log", "test debug") log.Warn("warn log") } func TestFilterCaller(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewFilter(logger) _ = log.Log(LevelDebug, "msg1", "te1st debug") logHelper := NewHelper(NewFilter(logger)) logHelper.Log(LevelDebug, "msg1", "te1st debug") } func TestFilterKey(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewHelper(NewFilter(logger, FilterKey("password"))) log.Debugw("password", "123456") } func TestFilterValue(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewHelper(NewFilter(logger, FilterValue("debug"))) log.Debugf("test %s", "debug") } func TestFilterFunc(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewHelper(NewFilter(logger, FilterFunc(testFilterFunc))) log.Debug("debug level") log.Infow("password", "123456") } func BenchmarkFilterKey(b *testing.B) { log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterKey("password"))) for i := 0; i < b.N; i++ { log.Infow("password", "123456") } } func BenchmarkFilterValue(b *testing.B) { log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterValue("password"))) for i := 0; i < b.N; i++ { log.Infow("password") } } func BenchmarkFilterFunc(b *testing.B) { log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterFunc(testFilterFunc))) for i := 0; i < b.N; i++ { log.Info("password", "123456") } } func testFilterFunc(level Level, keyvals ...any) bool { if level == LevelWarn { return true } for i := 0; i < len(keyvals); i++ { if keyvals[i] == "password" { keyvals[i+1] = fuzzyStr } } return false } func TestFilterFuncWitchLoggerPrefix(t *testing.T) { buf := new(bytes.Buffer) tests := []struct { logger Logger want string }{ { logger: NewFilter(With(NewStdLogger(buf), "caller", "caller", "prefix", "whatever"), FilterFunc(testFilterFuncWithLoggerPrefix)), want: "", }, { // Filtered value logger: NewFilter(With(NewStdLogger(buf), "caller", "caller"), FilterFunc(testFilterFuncWithLoggerPrefix)), want: "INFO caller=caller msg=msg filtered=***\n", }, { // NO prefix logger: NewFilter(With(NewStdLogger(buf)), FilterFunc(testFilterFuncWithLoggerPrefix)), want: "INFO msg=msg filtered=***\n", }, } for _, tt := range tests { err := tt.logger.Log(LevelInfo, "msg", "msg", "filtered", "true") if err != nil { t.Fatal("err should be nil") } got := buf.String() if got != tt.want { t.Fatalf("filter should catch prefix, want %s, got %s.", tt.want, got) } buf.Reset() } } func testFilterFuncWithLoggerPrefix(level Level, keyvals ...any) bool { if level == LevelWarn { return true } for i := 0; i < len(keyvals); i += 2 { if keyvals[i] == "prefix" { return true } if keyvals[i] == "filtered" { keyvals[i+1] = fuzzyStr } } return false } func TestFilterWithContext(t *testing.T) { type CtxKey struct { Key string } ctxKey := CtxKey{Key: "context"} ctxValue := "filter test value" v1 := func() Valuer { return func(ctx context.Context) any { return ctx.Value(ctxKey) } } info := &bytes.Buffer{} logger := With(NewStdLogger(info), "request_id", v1()) filter := NewFilter(logger, FilterLevel(LevelError)) ctx := context.WithValue(context.Background(), ctxKey, ctxValue) _ = WithContext(ctx, filter).Log(LevelInfo, "kind", "test") if info.String() != "" { t.Error("filter is not working") return } _ = WithContext(ctx, filter).Log(LevelError, "kind", "test") if !strings.Contains(info.String(), ctxValue) { t.Error("don't read ctx value") } } type traceIDKey struct{} func setTraceID(ctx context.Context, tid string) context.Context { return context.WithValue(ctx, traceIDKey{}, tid) } func traceIDValuer() Valuer { return func(ctx context.Context) any { if ctx == nil { return "" } if tid := ctx.Value(traceIDKey{}); tid != nil { return tid } return "" } } func TestFilterWithContextConcurrent(t *testing.T) { var buf bytes.Buffer pctx := context.Background() l := NewFilter( With(NewStdLogger(&buf), "trace-id", traceIDValuer()), FilterLevel(LevelInfo), ) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() time.Sleep(time.Second) NewHelper(l).Info("done1") }() wg.Add(1) go func() { defer wg.Done() tid := "world" ctx := setTraceID(pctx, tid) NewHelper((WithContext(ctx, l))).Info("done2") }() wg.Wait() expected := "INFO trace-id=world msg=done2\nINFO trace-id= msg=done1\n" if got := buf.String(); got != expected { t.Errorf("got: %#v", got) } } ================================================ FILE: log/global.go ================================================ package log import ( "context" "fmt" "os" "sync" ) // globalLogger is designed as a global logger in current process. var global = &loggerAppliance{} // loggerAppliance is the proxy of `Logger` to // make logger change will affect all sub-logger. type loggerAppliance struct { lock sync.RWMutex Logger } func init() { global.SetLogger(DefaultLogger) } func (a *loggerAppliance) SetLogger(in Logger) { a.lock.Lock() defer a.lock.Unlock() a.Logger = in } // SetLogger should be called before any other log call. // And it is NOT THREAD SAFE. func SetLogger(logger Logger) { global.SetLogger(logger) } // GetLogger returns global logger appliance as logger in current process. func GetLogger() Logger { global.lock.RLock() defer global.lock.RUnlock() return global.Logger } // Log Print log by level and keyvals. func Log(level Level, keyvals ...any) { _ = global.Log(level, keyvals...) } // Context with context logger. func Context(ctx context.Context) *Helper { return NewHelper(WithContext(ctx, global.Logger)) } // Debug logs a message at debug level. func Debug(a ...any) { _ = global.Log(LevelDebug, DefaultMessageKey, fmt.Sprint(a...)) } // Debugf logs a message at debug level. func Debugf(format string, a ...any) { _ = global.Log(LevelDebug, DefaultMessageKey, fmt.Sprintf(format, a...)) } // Debugw logs a message at debug level. func Debugw(keyvals ...any) { _ = global.Log(LevelDebug, keyvals...) } // Info logs a message at info level. func Info(a ...any) { _ = global.Log(LevelInfo, DefaultMessageKey, fmt.Sprint(a...)) } // Infof logs a message at info level. func Infof(format string, a ...any) { _ = global.Log(LevelInfo, DefaultMessageKey, fmt.Sprintf(format, a...)) } // Infow logs a message at info level. func Infow(keyvals ...any) { _ = global.Log(LevelInfo, keyvals...) } // Warn logs a message at warn level. func Warn(a ...any) { _ = global.Log(LevelWarn, DefaultMessageKey, fmt.Sprint(a...)) } // Warnf logs a message at warnf level. func Warnf(format string, a ...any) { _ = global.Log(LevelWarn, DefaultMessageKey, fmt.Sprintf(format, a...)) } // Warnw logs a message at warnf level. func Warnw(keyvals ...any) { _ = global.Log(LevelWarn, keyvals...) } // Error logs a message at error level. func Error(a ...any) { _ = global.Log(LevelError, DefaultMessageKey, fmt.Sprint(a...)) } // Errorf logs a message at error level. func Errorf(format string, a ...any) { _ = global.Log(LevelError, DefaultMessageKey, fmt.Sprintf(format, a...)) } // Errorw logs a message at error level. func Errorw(keyvals ...any) { _ = global.Log(LevelError, keyvals...) } // Fatal logs a message at fatal level. func Fatal(a ...any) { _ = global.Log(LevelFatal, DefaultMessageKey, fmt.Sprint(a...)) os.Exit(1) } // Fatalf logs a message at fatal level. func Fatalf(format string, a ...any) { _ = global.Log(LevelFatal, DefaultMessageKey, fmt.Sprintf(format, a...)) os.Exit(1) } // Fatalw logs a message at fatal level. func Fatalw(keyvals ...any) { _ = global.Log(LevelFatal, keyvals...) os.Exit(1) } ================================================ FILE: log/global_test.go ================================================ package log import ( "bytes" "context" "fmt" "os" "strings" "testing" ) func TestGlobalLog(t *testing.T) { buffer := &bytes.Buffer{} logger := NewStdLogger(buffer) SetLogger(logger) if global.Logger != logger { t.Error("GetLogger() is not equal to logger") } testCases := []struct { level Level content []any }{ { LevelDebug, []any{"test debug"}, }, { LevelInfo, []any{"test info"}, }, { LevelInfo, []any{"test %s", "info"}, }, { LevelWarn, []any{"test warn"}, }, { LevelError, []any{"test error"}, }, { LevelError, []any{"test %s", "error"}, }, } var expected []string for _, tc := range testCases { msg := fmt.Sprintf(tc.content[0].(string), tc.content[1:]...) switch tc.level { case LevelDebug: Debug(msg) expected = append(expected, fmt.Sprintf("%s msg=%s", "DEBUG", msg)) Debugf(tc.content[0].(string), tc.content[1:]...) expected = append(expected, fmt.Sprintf("%s msg=%s", "DEBUG", msg)) Debugw("log", msg) expected = append(expected, fmt.Sprintf("%s log=%s", "DEBUG", msg)) case LevelInfo: Info(msg) expected = append(expected, fmt.Sprintf("%s msg=%s", "INFO", msg)) Infof(tc.content[0].(string), tc.content[1:]...) expected = append(expected, fmt.Sprintf("%s msg=%s", "INFO", msg)) Infow("log", msg) expected = append(expected, fmt.Sprintf("%s log=%s", "INFO", msg)) case LevelWarn: Warn(msg) expected = append(expected, fmt.Sprintf("%s msg=%s", "WARN", msg)) Warnf(tc.content[0].(string), tc.content[1:]...) expected = append(expected, fmt.Sprintf("%s msg=%s", "WARN", msg)) Warnw("log", msg) expected = append(expected, fmt.Sprintf("%s log=%s", "WARN", msg)) case LevelError: Error(msg) expected = append(expected, fmt.Sprintf("%s msg=%s", "ERROR", msg)) Errorf(tc.content[0].(string), tc.content[1:]...) expected = append(expected, fmt.Sprintf("%s msg=%s", "ERROR", msg)) Errorw("log", msg) expected = append(expected, fmt.Sprintf("%s log=%s", "ERROR", msg)) } } Log(LevelInfo, DefaultMessageKey, "test log") expected = append(expected, fmt.Sprintf("%s msg=%s", "INFO", "test log")) expected = append(expected, "") t.Logf("Content: %s", buffer.String()) if buffer.String() != strings.Join(expected, "\n") { t.Errorf("Expected: %s, got: %s", strings.Join(expected, "\n"), buffer.String()) } } func TestGlobalLogUpdate(t *testing.T) { l := &loggerAppliance{} l.SetLogger(NewStdLogger(os.Stdout)) LOG := NewHelper(l) LOG.Info("Log to stdout") buffer := &bytes.Buffer{} l.SetLogger(NewStdLogger(buffer)) LOG.Info("Log to buffer") expected := "INFO msg=Log to buffer\n" if buffer.String() != expected { t.Errorf("Expected: %s, got: %s", expected, buffer.String()) } } func TestGlobalContext(t *testing.T) { buffer := &bytes.Buffer{} SetLogger(NewStdLogger(buffer)) Context(context.Background()).Infof("111") if buffer.String() != "INFO msg=111\n" { t.Errorf("Expected:%s, got:%s", "INFO msg=111", buffer.String()) } } func TestContextWithGlobalLog(t *testing.T) { buffer := &bytes.Buffer{} type traceKey struct{} // set "trace-id" Valuer newLogger := With(NewStdLogger(buffer), "trace-id", Valuer(func(ctx context.Context) any { return ctx.Value(traceKey{}) })) SetLogger(newLogger) // add value to ctx ctx := context.WithValue(context.Background(), traceKey{}, "test-trace-id") _ = WithContext(ctx, GetLogger()).Log(LevelInfo) if buffer.String() != "INFO trace-id=test-trace-id\n" { t.Errorf("Expected:%s, got:%s", "INFO trace-id=test-trace-id", buffer.String()) } } ================================================ FILE: log/helper.go ================================================ package log import ( "context" "fmt" "os" ) // DefaultMessageKey default message key. var DefaultMessageKey = "msg" // Option is Helper option. type Option func(*Helper) // Helper is a logger helper. type Helper struct { logger Logger msgKey string sprint func(...any) string sprintf func(format string, a ...any) string } // WithMessageKey with message key. func WithMessageKey(k string) Option { return func(opts *Helper) { opts.msgKey = k } } // WithSprint with sprint func WithSprint(sprint func(...any) string) Option { return func(opts *Helper) { opts.sprint = sprint } } // WithSprintf with sprintf func WithSprintf(sprintf func(format string, a ...any) string) Option { return func(opts *Helper) { opts.sprintf = sprintf } } // NewHelper new a logger helper. func NewHelper(logger Logger, opts ...Option) *Helper { options := &Helper{ msgKey: DefaultMessageKey, // default message key logger: logger, sprint: fmt.Sprint, sprintf: fmt.Sprintf, } for _, o := range opts { o(options) } return options } // WithContext returns a shallow copy of h with its context changed // to ctx. The provided ctx must be non-nil. func (h *Helper) WithContext(ctx context.Context) *Helper { return &Helper{ msgKey: h.msgKey, logger: WithContext(ctx, h.logger), sprint: h.sprint, sprintf: h.sprintf, } } // Enabled returns true if the given level above this level. // It delegates to the underlying *Filter. func (h *Helper) Enabled(level Level) bool { if l, ok := h.logger.(*Filter); ok { return level >= l.level } return true } // Logger returns logger in the helper. func (h *Helper) Logger() Logger { return h.logger } // Log Print log by level and keyvals. func (h *Helper) Log(level Level, keyvals ...any) { _ = h.logger.Log(level, keyvals...) } // Debug logs a message at debug level. func (h *Helper) Debug(a ...any) { if !h.Enabled(LevelDebug) { return } _ = h.logger.Log(LevelDebug, h.msgKey, h.sprint(a...)) } // Debugf logs a message at debug level. func (h *Helper) Debugf(format string, a ...any) { if !h.Enabled(LevelDebug) { return } _ = h.logger.Log(LevelDebug, h.msgKey, h.sprintf(format, a...)) } // Debugw logs a message at debug level. func (h *Helper) Debugw(keyvals ...any) { _ = h.logger.Log(LevelDebug, keyvals...) } // Info logs a message at info level. func (h *Helper) Info(a ...any) { if !h.Enabled(LevelInfo) { return } _ = h.logger.Log(LevelInfo, h.msgKey, h.sprint(a...)) } // Infof logs a message at info level. func (h *Helper) Infof(format string, a ...any) { if !h.Enabled(LevelInfo) { return } _ = h.logger.Log(LevelInfo, h.msgKey, h.sprintf(format, a...)) } // Infow logs a message at info level. func (h *Helper) Infow(keyvals ...any) { _ = h.logger.Log(LevelInfo, keyvals...) } // Warn logs a message at warn level. func (h *Helper) Warn(a ...any) { if !h.Enabled(LevelWarn) { return } _ = h.logger.Log(LevelWarn, h.msgKey, h.sprint(a...)) } // Warnf logs a message at warnf level. func (h *Helper) Warnf(format string, a ...any) { if !h.Enabled(LevelWarn) { return } _ = h.logger.Log(LevelWarn, h.msgKey, h.sprintf(format, a...)) } // Warnw logs a message at warnf level. func (h *Helper) Warnw(keyvals ...any) { _ = h.logger.Log(LevelWarn, keyvals...) } // Error logs a message at error level. func (h *Helper) Error(a ...any) { if !h.Enabled(LevelError) { return } _ = h.logger.Log(LevelError, h.msgKey, h.sprint(a...)) } // Errorf logs a message at error level. func (h *Helper) Errorf(format string, a ...any) { if !h.Enabled(LevelError) { return } _ = h.logger.Log(LevelError, h.msgKey, h.sprintf(format, a...)) } // Errorw logs a message at error level. func (h *Helper) Errorw(keyvals ...any) { _ = h.logger.Log(LevelError, keyvals...) } // Fatal logs a message at fatal level. func (h *Helper) Fatal(a ...any) { _ = h.logger.Log(LevelFatal, h.msgKey, h.sprint(a...)) os.Exit(1) } // Fatalf logs a message at fatal level. func (h *Helper) Fatalf(format string, a ...any) { _ = h.logger.Log(LevelFatal, h.msgKey, h.sprintf(format, a...)) os.Exit(1) } // Fatalw logs a message at fatal level. func (h *Helper) Fatalw(keyvals ...any) { _ = h.logger.Log(LevelFatal, keyvals...) os.Exit(1) } ================================================ FILE: log/helper_test.go ================================================ package log import ( "context" "io" "os" "testing" ) func TestHelper(_ *testing.T) { logger := With( DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller, "module", "test", ) log := NewHelper(logger) log.Log(LevelDebug, "msg", "test debug") log.Debug("test debug") log.Debugf("test %s", "debug") log.Debugw("log", "test debug") log.Warn("test warn") log.Warnf("test %s", "warn") log.Warnw("log", "test warn") subLogger := With(log.Logger(), "module", "sub", ) subLog := NewHelper(subLogger) subLog.Infof("sub logger test with level %s", "info") } func TestHelperWithMsgKey(_ *testing.T) { logger := With(DefaultLogger, "ts", DefaultTimestamp, "caller", DefaultCaller) log := NewHelper(logger, WithMessageKey("message")) log.Debugf("test %s", "debug") log.Debugw("log", "test debug") } func TestHelperLevel(_ *testing.T) { log := NewHelper(DefaultLogger) log.Debug("test debug") log.Info("test info") log.Infof("test %s", "info") log.Warn("test warn") log.Error("test error") log.Errorf("test %s", "error") log.Errorw("log", "test error") } func BenchmarkHelperPrint(b *testing.B) { log := NewHelper(NewStdLogger(io.Discard)) for i := 0; i < b.N; i++ { log.Debug("test") } } func BenchmarkHelperPrintFilterLevel(b *testing.B) { log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterLevel(LevelDebug))) for i := 0; i < b.N; i++ { log.Debug("test") } } func BenchmarkHelperPrintf(b *testing.B) { log := NewHelper(NewStdLogger(io.Discard)) for i := 0; i < b.N; i++ { log.Debugf("%s", "test") } } func BenchmarkHelperPrintfFilterLevel(b *testing.B) { log := NewHelper(NewFilter(NewStdLogger(io.Discard), FilterLevel(LevelInfo))) for i := 0; i < b.N; i++ { log.Debugf("%s", "test") } } func BenchmarkHelperPrintw(b *testing.B) { log := NewHelper(NewStdLogger(io.Discard)) for i := 0; i < b.N; i++ { log.Debugw("key", "value") } } type traceKey struct{} func TestContext(_ *testing.T) { logger := With(NewStdLogger(os.Stdout), "trace", Trace(), ) log := NewHelper(logger) ctx := context.WithValue(context.Background(), traceKey{}, "2233") log.WithContext(ctx).Info("got trace!") } func Trace() Valuer { return func(ctx context.Context) any { s, ok := ctx.Value(traceKey{}).(string) if !ok { return nil } return s } } ================================================ FILE: log/helper_writer.go ================================================ package log import "io" type writerWrapper struct { helper *Helper level Level } type WriterOptionFn func(w *writerWrapper) // WithWriterLevel set writerWrapper level. func WithWriterLevel(level Level) WriterOptionFn { return func(w *writerWrapper) { w.level = level } } // WithWriteMessageKey set writerWrapper helper message key. func WithWriteMessageKey(key string) WriterOptionFn { return func(w *writerWrapper) { w.helper.msgKey = key } } // NewWriter return a writer wrapper. func NewWriter(logger Logger, opts ...WriterOptionFn) io.Writer { ww := &writerWrapper{ helper: NewHelper(logger, WithMessageKey(DefaultMessageKey)), level: LevelInfo, // default level } for _, opt := range opts { opt(ww) } return ww } func (ww *writerWrapper) Write(p []byte) (int, error) { ww.helper.Log(ww.level, ww.helper.msgKey, string(p)) return 0, nil } ================================================ FILE: log/helper_writer_test.go ================================================ package log import ( "bytes" "io" "strings" "testing" ) func TestWriterWrapper(t *testing.T) { var buf bytes.Buffer logger := NewStdLogger(&buf) content := "ThisIsSomeTestLogMessage" testCases := []struct { w io.Writer acceptLevel Level acceptMessageKey string }{ { w: NewWriter(logger), acceptLevel: LevelInfo, // default level acceptMessageKey: DefaultMessageKey, }, { w: NewWriter(logger, WithWriterLevel(LevelDebug)), acceptLevel: LevelDebug, acceptMessageKey: DefaultMessageKey, }, { w: NewWriter(logger, WithWriteMessageKey("XxXxX")), acceptLevel: LevelInfo, // default level acceptMessageKey: "XxXxX", }, { w: NewWriter(logger, WithWriterLevel(LevelError), WithWriteMessageKey("XxXxX")), acceptLevel: LevelError, acceptMessageKey: "XxXxX", }, } for _, tc := range testCases { _, err := tc.w.Write([]byte(content)) if err != nil { t.Fatalf("unexpected error: %v", err) } if !strings.Contains(buf.String(), tc.acceptLevel.String()) { t.Errorf("expected level: %s, got: %s", tc.acceptLevel, buf.String()) } if !strings.Contains(buf.String(), tc.acceptMessageKey) { t.Errorf("expected message key: %s, got: %s", tc.acceptMessageKey, buf.String()) } } } ================================================ FILE: log/level.go ================================================ package log import "strings" // Level is a logger level. type Level int8 // LevelKey is logger level key. const LevelKey = "level" const ( // LevelDebug is logger debug level. LevelDebug Level = iota - 1 // LevelInfo is logger info level. LevelInfo // LevelWarn is logger warn level. LevelWarn // LevelError is logger error level. LevelError // LevelFatal is logger fatal level LevelFatal ) func (l Level) Key() string { return LevelKey } func (l Level) String() string { switch l { case LevelDebug: return "DEBUG" case LevelInfo: return "INFO" case LevelWarn: return "WARN" case LevelError: return "ERROR" case LevelFatal: return "FATAL" default: return "" } } // ParseLevel parses a level string into a logger Level value. func ParseLevel(s string) Level { switch strings.ToUpper(s) { case "DEBUG": return LevelDebug case "INFO": return LevelInfo case "WARN": return LevelWarn case "ERROR": return LevelError case "FATAL": return LevelFatal } return LevelInfo } ================================================ FILE: log/level_test.go ================================================ package log import "testing" func TestLevel_Key(t *testing.T) { if LevelInfo.Key() != LevelKey { t.Errorf("want: %s, got: %s", LevelKey, LevelInfo.Key()) } } func TestLevel_String(t *testing.T) { tests := []struct { name string l Level want string }{ { name: "DEBUG", l: LevelDebug, want: "DEBUG", }, { name: "INFO", l: LevelInfo, want: "INFO", }, { name: "WARN", l: LevelWarn, want: "WARN", }, { name: "ERROR", l: LevelError, want: "ERROR", }, { name: "FATAL", l: LevelFatal, want: "FATAL", }, { name: "other", l: 10, want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.l.String(); got != tt.want { t.Errorf("String() = %v, want %v", got, tt.want) } }) } } func TestParseLevel(t *testing.T) { tests := []struct { name string s string want Level }{ { name: "DEBUG", want: LevelDebug, s: "DEBUG", }, { name: "INFO", want: LevelInfo, s: "INFO", }, { name: "WARN", want: LevelWarn, s: "WARN", }, { name: "ERROR", want: LevelError, s: "ERROR", }, { name: "FATAL", want: LevelFatal, s: "FATAL", }, { name: "other", want: LevelInfo, s: "other", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ParseLevel(tt.s); got != tt.want { t.Errorf("ParseLevel() = %v, want %v", got, tt.want) } }) } } ================================================ FILE: log/log.go ================================================ package log import ( "context" "log" ) // DefaultLogger is default logger. var DefaultLogger = NewStdLogger(log.Writer()) // Logger is a logger interface. type Logger interface { Log(level Level, keyvals ...any) error } type logger struct { logger Logger prefix []any hasValuer bool ctx context.Context } func (c *logger) Log(level Level, keyvals ...any) error { kvs := make([]any, 0, len(c.prefix)+len(keyvals)) kvs = append(kvs, c.prefix...) if c.hasValuer { bindValues(c.ctx, kvs) } kvs = append(kvs, keyvals...) return c.logger.Log(level, kvs...) } // With with logger fields. func With(l Logger, kv ...any) Logger { c, ok := l.(*logger) if !ok { return &logger{logger: l, prefix: kv, hasValuer: containsValuer(kv), ctx: context.Background()} } kvs := make([]any, 0, len(c.prefix)+len(kv)) kvs = append(kvs, c.prefix...) kvs = append(kvs, kv...) return &logger{ logger: c.logger, prefix: kvs, hasValuer: containsValuer(kvs), ctx: c.ctx, } } // WithContext returns a shallow copy of l with its context changed // to ctx. The provided ctx must be non-nil. func WithContext(ctx context.Context, l Logger) Logger { switch v := l.(type) { default: return &logger{logger: l, ctx: ctx} case *logger: lv := *v lv.ctx = ctx return &lv case *Filter: fv := *v fv.logger = WithContext(ctx, fv.logger) return &fv } } ================================================ FILE: log/log_test.go ================================================ package log import ( "testing" ) func TestInfo(_ *testing.T) { logger := DefaultLogger logger = With(logger, "ts", DefaultTimestamp) logger = With(logger, "caller", DefaultCaller) _ = logger.Log(LevelInfo, "key1", "value1") } ================================================ FILE: log/std.go ================================================ package log import ( "bytes" "fmt" "io" "sync" ) var _ Logger = (*stdLogger)(nil) // stdLogger corresponds to the standard library's [log.Logger] and provides // similar capabilities. It also can be used concurrently by multiple goroutines. type stdLogger struct { w io.Writer isDiscard bool mu sync.Mutex pool *sync.Pool } // NewStdLogger new a logger with writer. func NewStdLogger(w io.Writer) Logger { return &stdLogger{ w: w, isDiscard: w == io.Discard, pool: &sync.Pool{ New: func() any { return new(bytes.Buffer) }, }, } } // Log print the kv pairs log. func (l *stdLogger) Log(level Level, keyvals ...any) error { if l.isDiscard || len(keyvals) == 0 { return nil } if (len(keyvals) & 1) == 1 { keyvals = append(keyvals, "KEYVALS UNPAIRED") } buf := l.pool.Get().(*bytes.Buffer) defer l.pool.Put(buf) buf.WriteString(level.String()) for i := 0; i < len(keyvals); i += 2 { _, _ = fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1]) } buf.WriteByte('\n') defer buf.Reset() l.mu.Lock() defer l.mu.Unlock() _, err := l.w.Write(buf.Bytes()) return err } func (l *stdLogger) Close() error { return nil } ================================================ FILE: log/std_test.go ================================================ package log import ( "bytes" "testing" "golang.org/x/sync/errgroup" ) func TestStdLogger(_ *testing.T) { logger := DefaultLogger logger = With(logger, "caller", DefaultCaller, "ts", DefaultTimestamp) _ = logger.Log(LevelInfo, "msg", "test debug") _ = logger.Log(LevelInfo, "msg", "test info") _ = logger.Log(LevelInfo, "msg", "test warn") _ = logger.Log(LevelInfo, "msg", "test error") _ = logger.Log(LevelDebug, "singular") logger2 := DefaultLogger _ = logger2.Log(LevelDebug) } func TestStdLogger_Log(t *testing.T) { var b bytes.Buffer logger := NewStdLogger(&b) var eg errgroup.Group eg.Go(func() error { return logger.Log(LevelInfo, "msg", "a", "k", "v") }) eg.Go(func() error { return logger.Log(LevelInfo, "msg", "a", "k", "v") }) err := eg.Wait() if err != nil { t.Fatalf("log error: %v", err) } if s := b.String(); s != "INFO msg=a k=v\nINFO msg=a k=v\n" { t.Fatalf("log not match: %q", s) } } ================================================ FILE: log/value.go ================================================ package log import ( "context" "runtime" "strconv" "strings" "time" ) var ( // DefaultCaller is a Valuer that returns the file and line. DefaultCaller = Caller(4) // DefaultTimestamp is a Valuer that returns the current wallclock time. DefaultTimestamp = Timestamp(time.RFC3339) ) // Valuer is returns a log value. type Valuer func(ctx context.Context) any // Value return the function value. func Value(ctx context.Context, v any) any { if v, ok := v.(Valuer); ok { return v(ctx) } return v } // Caller returns a Valuer that returns a pkg/file:line description of the caller. func Caller(depth int) Valuer { return func(context.Context) any { _, file, line, _ := runtime.Caller(depth) idx := strings.LastIndexByte(file, '/') if idx == -1 { return file[idx+1:] + ":" + strconv.Itoa(line) } idx = strings.LastIndexByte(file[:idx], '/') return file[idx+1:] + ":" + strconv.Itoa(line) } } // Timestamp returns a timestamp Valuer with a custom time format. func Timestamp(layout string) Valuer { return func(context.Context) any { return time.Now().Format(layout) } } func bindValues(ctx context.Context, keyvals []any) { for i := 1; i < len(keyvals); i += 2 { if v, ok := keyvals[i].(Valuer); ok { keyvals[i] = v(ctx) } } } func containsValuer(keyvals []any) bool { for i := 1; i < len(keyvals); i += 2 { if _, ok := keyvals[i].(Valuer); ok { return true } } return false } ================================================ FILE: log/value_test.go ================================================ package log import ( "context" "testing" ) func TestValue(t *testing.T) { logger := DefaultLogger logger = With(logger, "ts", DefaultTimestamp, "caller", DefaultCaller) _ = logger.Log(LevelInfo, "msg", "helloworld") logger = DefaultLogger logger = With(logger) _ = logger.Log(LevelDebug, "msg", "helloworld") var v1 any got := Value(context.Background(), v1) if got != v1 { t.Errorf("Value() = %v, want %v", got, v1) } var v2 Valuer = func(context.Context) any { return 3 } got = Value(context.Background(), v2) res := got.(int) if res != 3 { t.Errorf("Value() = %v, want %v", res, 3) } } ================================================ FILE: metadata/metadata.go ================================================ package metadata import ( "context" "fmt" "slices" "strings" ) // Metadata is our way of representing request headers internally. // They're used at the RPC level and translate back and forth // from Transport headers. type Metadata map[string][]string // New creates an MD from a given key-values map. func New(mds ...map[string][]string) Metadata { md := Metadata{} for _, m := range mds { for k, vList := range m { for _, v := range vList { md.Add(k, v) } } } return md } // Add adds the key, value pair to the header. func (m Metadata) Add(key, value string) { if key == "" { return } lowerKey := strings.ToLower(key) m[lowerKey] = append(m[lowerKey], value) } // Get returns the value associated with the passed key. func (m Metadata) Get(key string) string { v := m[strings.ToLower(key)] if len(v) == 0 { return "" } return v[0] } // Set stores the key-value pair. func (m Metadata) Set(key string, value string) { if key == "" || value == "" { return } m[strings.ToLower(key)] = []string{value} } // Range iterate over element in metadata. func (m Metadata) Range(f func(k string, v []string) bool) { for k, v := range m { if !f(k, v) { break } } } // Values returns a slice of values associated with the passed key. func (m Metadata) Values(key string) []string { return m[strings.ToLower(key)] } // Clone returns a deep copy of Metadata func (m Metadata) Clone() Metadata { md := make(Metadata, len(m)) for k, v := range m { md[k] = slices.Clone(v) } return md } type serverMetadataKey struct{} // NewServerContext creates a new context with client md attached. func NewServerContext(ctx context.Context, md Metadata) context.Context { return context.WithValue(ctx, serverMetadataKey{}, md) } // FromServerContext returns the server metadata in ctx if it exists. func FromServerContext(ctx context.Context) (Metadata, bool) { md, ok := ctx.Value(serverMetadataKey{}).(Metadata) return md, ok } type clientMetadataKey struct{} // NewClientContext creates a new context with client md attached. func NewClientContext(ctx context.Context, md Metadata) context.Context { return context.WithValue(ctx, clientMetadataKey{}, md) } // FromClientContext returns the client metadata in ctx if it exists. func FromClientContext(ctx context.Context) (Metadata, bool) { md, ok := ctx.Value(clientMetadataKey{}).(Metadata) return md, ok } // AppendToClientContext returns a new context with the provided kv merged // with any existing metadata in the context. func AppendToClientContext(ctx context.Context, kv ...string) context.Context { if len(kv)%2 == 1 { panic(fmt.Sprintf("metadata: AppendToClientContext got an odd number of input pairs for metadata: %d", len(kv))) } md, _ := FromClientContext(ctx) md = md.Clone() for i := 0; i < len(kv); i += 2 { md.Set(kv[i], kv[i+1]) } return NewClientContext(ctx, md) } // MergeToClientContext merge new metadata into ctx. func MergeToClientContext(ctx context.Context, cmd Metadata) context.Context { md, _ := FromClientContext(ctx) md = md.Clone() for k, v := range cmd { md[k] = v } return NewClientContext(ctx, md) } ================================================ FILE: metadata/metadata_test.go ================================================ package metadata import ( "context" "reflect" "testing" ) func TestNew(t *testing.T) { type args struct { mds []map[string][]string } tests := []struct { name string args args want Metadata }{ { name: "hello", args: args{[]map[string][]string{{"hello": {"kratos"}}, {"hello2": {"go-kratos"}}}}, want: Metadata{"hello": {"kratos"}, "hello2": {"go-kratos"}}, }, { name: "hi", args: args{[]map[string][]string{{"hi": {"kratos"}}, {"hi2": {"go-kratos"}}}}, want: Metadata{"hi": {"kratos"}, "hi2": {"go-kratos"}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := New(tt.args.mds...); !reflect.DeepEqual(got, tt.want) { t.Errorf("New() = %v, want %v", got, tt.want) } }) } } func TestMetadata_Get(t *testing.T) { type args struct { key string } tests := []struct { name string m Metadata args args want string }{ { name: "kratos", m: Metadata{"kratos": {"value"}, "env": {"dev"}}, args: args{key: "kratos"}, want: "value", }, { name: "env", m: Metadata{"kratos": {"value"}, "env": {"dev"}}, args: args{key: "env"}, want: "dev", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.m.Get(tt.args.key); got != tt.want { t.Errorf("Get() = %v, want %v", got, tt.want) } }) } } func TestMetadata_Values(t *testing.T) { type args struct { key string } tests := []struct { name string m Metadata args args want []string }{ { name: "kratos", m: Metadata{"kratos": {"value", "value2"}, "env": {"dev"}}, args: args{key: "kratos"}, want: []string{"value", "value2"}, }, { name: "env", m: Metadata{"kratos": {"value", "value2"}, "env": {"dev"}}, args: args{key: "env"}, want: []string{"dev"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.m.Values(tt.args.key); !reflect.DeepEqual(got, tt.want) { t.Errorf("Get() = %v, want %v", got, tt.want) } }) } } func TestMetadata_Set(t *testing.T) { type args struct { key string value string } tests := []struct { name string m Metadata args args want Metadata }{ { name: "kratos", m: Metadata{}, args: args{key: "hello", value: "kratos"}, want: Metadata{"hello": {"kratos"}}, }, { name: "env", m: Metadata{"hello": {"kratos"}}, args: args{key: "env", value: "pro"}, want: Metadata{"hello": {"kratos"}, "env": {"pro"}}, }, { name: "empty", m: Metadata{}, args: args{key: "", value: ""}, want: Metadata{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.m.Set(tt.args.key, tt.args.value) if !reflect.DeepEqual(tt.m, tt.want) { t.Errorf("Set() = %v, want %v", tt.m, tt.want) } }) } } func TestMetadata_Add(t *testing.T) { type args struct { key string value string } tests := []struct { name string m Metadata args args want Metadata }{ { name: "kratos", m: Metadata{}, args: args{key: "hello", value: "kratos"}, want: Metadata{"hello": {"kratos"}}, }, { name: "env", m: Metadata{"hello": {"kratos"}}, args: args{key: "hello", value: "again"}, want: Metadata{"hello": {"kratos", "again"}}, }, { name: "empty", m: Metadata{}, args: args{key: "", value: ""}, want: Metadata{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.m.Add(tt.args.key, tt.args.value) if !reflect.DeepEqual(tt.m, tt.want) { t.Errorf("Set() = %v, want %v", tt.m, tt.want) } }) } } func TestClientContext(t *testing.T) { type args struct { ctx context.Context md Metadata } tests := []struct { name string args args }{ { name: "kratos", args: args{context.Background(), Metadata{"hello": {"kratos"}, "kratos": {"https://go-kratos.dev"}}}, }, { name: "hello", args: args{context.Background(), Metadata{"hello": {"kratos"}, "hello2": {"https://go-kratos.dev"}}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := NewClientContext(tt.args.ctx, tt.args.md) m, ok := FromClientContext(ctx) if !ok { t.Errorf("FromClientContext() = %v, want %v", ok, true) } if !reflect.DeepEqual(m, tt.args.md) { t.Errorf("meta = %v, want %v", m, tt.args.md) } }) } } func TestServerContext(t *testing.T) { type args struct { ctx context.Context md Metadata } tests := []struct { name string args args }{ { name: "kratos", args: args{context.Background(), Metadata{"hello": {"kratos"}, "kratos": {"https://go-kratos.dev"}}}, }, { name: "hello", args: args{context.Background(), Metadata{"hello": {"kratos"}, "hello2": {"https://go-kratos.dev"}}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := NewServerContext(tt.args.ctx, tt.args.md) m, ok := FromServerContext(ctx) if !ok { t.Errorf("FromServerContext() = %v, want %v", ok, true) } if !reflect.DeepEqual(m, tt.args.md) { t.Errorf("meta = %v, want %v", m, tt.args.md) } }) } } func TestAppendToClientContext(t *testing.T) { type args struct { md Metadata kv []string } tests := []struct { name string args args want Metadata }{ { name: "kratos", args: args{Metadata{}, []string{"hello", "kratos", "env", "dev"}}, want: Metadata{"hello": {"kratos"}, "env": {"dev"}}, }, { name: "hello", args: args{Metadata{"hi": {"https://go-kratos.dev/"}}, []string{"hello", "kratos", "env", "dev"}}, want: Metadata{"hello": {"kratos"}, "env": {"dev"}, "hi": {"https://go-kratos.dev/"}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := NewClientContext(context.Background(), tt.args.md) ctx = AppendToClientContext(ctx, tt.args.kv...) md, ok := FromClientContext(ctx) if !ok { t.Errorf("FromServerContext() = %v, want %v", ok, true) } if !reflect.DeepEqual(md, tt.want) { t.Errorf("metadata = %v, want %v", md, tt.want) } }) } } // nolint directives: sa5012 func TestAppendToClientContextThatPanics(t *testing.T) { kvs := []string{"hello", "kratos", "env"} defer func() { if r := recover(); r == nil { t.Errorf("append to client context singular kvs did not panic") } }() ctx := NewClientContext(context.Background(), Metadata{}) ctx = AppendToClientContext(ctx, kvs...) md, ok := FromClientContext(ctx) if !ok { t.Errorf("FromServerContext() = %v, want %v", ok, true) } if !reflect.DeepEqual(md, Metadata{}) { t.Errorf("metadata = %v, want %v", md, Metadata{}) } } func TestMergeToClientContext(t *testing.T) { type args struct { md Metadata appendMd Metadata } tests := []struct { name string args args want Metadata }{ { name: "kratos", args: args{Metadata{}, Metadata{"hello": {"kratos"}, "env": {"dev"}}}, want: Metadata{"hello": {"kratos"}, "env": {"dev"}}, }, { name: "hello", args: args{Metadata{"hi": {"https://go-kratos.dev/"}}, Metadata{"hello": {"kratos"}, "env": {"dev"}}}, want: Metadata{"hello": {"kratos"}, "env": {"dev"}, "hi": {"https://go-kratos.dev/"}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := NewClientContext(context.Background(), tt.args.md) ctx = MergeToClientContext(ctx, tt.args.appendMd) md, ok := FromClientContext(ctx) if !ok { t.Errorf("FromServerContext() = %v, want %v", ok, true) } if !reflect.DeepEqual(md, tt.want) { t.Errorf("metadata = %v, want %v", md, tt.want) } }) } } func TestMetadata_Range(t *testing.T) { md := Metadata{"kratos": {"kratos"}, "https://go-kratos.dev/": {"https://go-kratos.dev/"}, "go-kratos": {"go-kratos"}} tmp := Metadata{} md.Range(func(k string, v []string) bool { if k == "https://go-kratos.dev/" || k == "kratos" { tmp[k] = v } return true }) if !reflect.DeepEqual(tmp, Metadata{"https://go-kratos.dev/": {"https://go-kratos.dev/"}, "kratos": {"kratos"}}) { t.Errorf("metadata = %v, want %v", tmp, Metadata{"https://go-kratos.dev/": {"https://go-kratos.dev/"}, "kratos": {"kratos"}}) } tmp = Metadata{} md.Range(func(string, []string) bool { return false }) if !reflect.DeepEqual(tmp, Metadata{}) { t.Errorf("metadata = %v, want %v", tmp, Metadata{}) } } func TestMetadata_Clone(t *testing.T) { tests := []struct { name string m Metadata want Metadata }{ { name: "kratos", m: Metadata{"kratos": {"kratos"}, "https://go-kratos.dev/": {"https://go-kratos.dev/"}, "go-kratos": {"go-kratos"}}, want: Metadata{"kratos": {"kratos"}, "https://go-kratos.dev/": {"https://go-kratos.dev/"}, "go-kratos": {"go-kratos"}}, }, { name: "go", m: Metadata{"language": {"golang"}}, want: Metadata{"language": {"golang"}}, }, { name: "plan9", m: Metadata{"k0": []string{}, "k1": nil}, want: Metadata{"k0": []string{}, "k1": nil}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.m.Clone() if !reflect.DeepEqual(got, tt.want) { t.Errorf("Clone() = %v, want %v", got, tt.want) } got["kratos"] = []string{"go"} if reflect.DeepEqual(got, tt.want) { t.Errorf("want got != want got %v want %v", got, tt.want) } }) } } // TestMetadata_CloneDeepCopy tests that Clone creates a deep copy of metadata, // so modifications to the original metadata's slices don't affect the cloned one. func TestMetadata_CloneDeepCopy(t *testing.T) { original := Metadata{ "test-key": {"value1", "value2", "value3"}, "single-key": {"single-value"}, } cloned := original.Clone() // Test 1: Modify an element in the original metadata's slice { original["test-key"][1] = "modified-value" // Verify that the cloned metadata's slice is not affected if cloned["test-key"][1] != "value2" { t.Errorf("Clone() modify leaked: original=%v, cloned=%v", original["test-key"], cloned["test-key"]) } } // Test 2: Append to the original metadata's slice { original["test-key"] = append(original["test-key"], "new-value") if len(cloned["test-key"]) != 3 { t.Errorf("Clone() append leaked: original len=%d, cloned len=%d", len(original["test-key"]), len(cloned["test-key"])) } expected := []string{"value1", "value2", "value3"} if !reflect.DeepEqual(cloned["test-key"], expected) { t.Errorf("Clone() append values: got=%v, want=%v", cloned["test-key"], expected) } } // Test 3: Replace the entire slice in the original metadata { original["single-key"] = []string{"replaced-value"} if cloned["single-key"][0] != "single-value" { t.Errorf("Clone() replace leaked: original=%v, cloned=%v", original["single-key"], cloned["single-key"]) } } } ================================================ FILE: middleware/auth/jwt/jwt.go ================================================ package jwt import ( "context" "fmt" "strings" "github.com/golang-jwt/jwt/v5" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) type authKey struct{} const ( // bearerWord the bearer key word for authorization bearerWord string = "Bearer" // bearerFormat authorization token format bearerFormat string = "Bearer %s" // authorizationKey holds the key used to store the JWT Token in the request tokenHeader. authorizationKey string = "Authorization" // reason holds the error reason. reason string = "UNAUTHORIZED" ) var ( ErrMissingJwtToken = errors.Unauthorized(reason, "JWT token is missing") ErrMissingKeyFunc = errors.Unauthorized(reason, "keyFunc is missing") ErrTokenInvalid = errors.Unauthorized(reason, "Token is invalid") ErrTokenExpired = errors.Unauthorized(reason, "JWT token has expired") ErrTokenParseFail = errors.Unauthorized(reason, "Fail to parse JWT token ") ErrUnSupportSigningMethod = errors.Unauthorized(reason, "Wrong signing method") ErrWrongContext = errors.Unauthorized(reason, "Wrong context for middleware") ErrNeedTokenProvider = errors.Unauthorized(reason, "Token provider is missing") ErrSignToken = errors.Unauthorized(reason, "Can not sign token.Is the key correct?") ErrGetKey = errors.Unauthorized(reason, "Can not get key while signing token") ) // Option is jwt option. type Option func(*options) // Parser is a jwt parser type options struct { signingMethod jwt.SigningMethod claims func() jwt.Claims tokenHeader map[string]any parserOptions []jwt.ParserOption } // WithSigningMethod with signing method option. func WithSigningMethod(method jwt.SigningMethod) Option { return func(o *options) { o.signingMethod = method } } // WithClaims with customer claim // If you use it in Server, f needs to return a new jwt.Claims object each time to avoid concurrent write problems // If you use it in Client, f only needs to return a single object to provide performance func WithClaims(f func() jwt.Claims) Option { return func(o *options) { o.claims = f } } // WithTokenHeader withe customer tokenHeader for client side func WithTokenHeader(header map[string]any) Option { return func(o *options) { o.tokenHeader = header } } // WithParserOptions with custom parser options for the jwt parser. // It allows customization of the jwt.Parser used during token validation, // for example, to enforce mandatory claim validations such as issuer, subject, or expiration. func WithParserOptions(opts ...jwt.ParserOption) Option { return func(o *options) { o.parserOptions = opts } } // Server is a server auth middleware. Check the token and extract the info from token. func Server(keyFunc jwt.Keyfunc, opts ...Option) middleware.Middleware { o := &options{ signingMethod: jwt.SigningMethodHS256, } for _, opt := range opts { opt(o) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (any, error) { if header, ok := transport.FromServerContext(ctx); ok { if keyFunc == nil { return nil, ErrMissingKeyFunc } auths := strings.SplitN(header.RequestHeader().Get(authorizationKey), " ", 2) if len(auths) != 2 || !strings.EqualFold(auths[0], bearerWord) { return nil, ErrMissingJwtToken } jwtToken := auths[1] var ( tokenInfo *jwt.Token err error ) if o.claims != nil { tokenInfo, err = jwt.ParseWithClaims(jwtToken, o.claims(), keyFunc, o.parserOptions...) } else { tokenInfo, err = jwt.Parse(jwtToken, keyFunc, o.parserOptions...) } if err != nil { if errors.Is(err, jwt.ErrTokenMalformed) || errors.Is(err, jwt.ErrTokenUnverifiable) { return nil, ErrTokenInvalid } if errors.Is(err, jwt.ErrTokenNotValidYet) || errors.Is(err, jwt.ErrTokenExpired) { return nil, ErrTokenExpired } return nil, ErrTokenParseFail } if !tokenInfo.Valid { return nil, ErrTokenInvalid } if tokenInfo.Method != o.signingMethod { return nil, ErrUnSupportSigningMethod } ctx = NewContext(ctx, tokenInfo.Claims) return handler(ctx, req) } return nil, ErrWrongContext } } } // Client is a client jwt middleware. func Client(keyProvider jwt.Keyfunc, opts ...Option) middleware.Middleware { claims := jwt.RegisteredClaims{} o := &options{ signingMethod: jwt.SigningMethodHS256, claims: func() jwt.Claims { return claims }, } for _, opt := range opts { opt(o) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (any, error) { if keyProvider == nil { return nil, ErrNeedTokenProvider } token := jwt.NewWithClaims(o.signingMethod, o.claims()) if o.tokenHeader != nil { for k, v := range o.tokenHeader { token.Header[k] = v } } key, err := keyProvider(token) if err != nil { return nil, ErrGetKey } tokenStr, err := token.SignedString(key) if err != nil { return nil, ErrSignToken } if clientContext, ok := transport.FromClientContext(ctx); ok { clientContext.RequestHeader().Set(authorizationKey, fmt.Sprintf(bearerFormat, tokenStr)) return handler(ctx, req) } return nil, ErrWrongContext } } } // NewContext put auth info into context func NewContext(ctx context.Context, info jwt.Claims) context.Context { return context.WithValue(ctx, authKey{}, info) } // FromContext extract auth info from context func FromContext(ctx context.Context) (token jwt.Claims, ok bool) { token, ok = ctx.Value(authKey{}).(jwt.Claims) return } ================================================ FILE: middleware/auth/jwt/jwt_test.go ================================================ package jwt import ( "context" "errors" "fmt" "math/rand/v2" "net/http" "reflect" "strconv" "sync" "testing" "time" "github.com/golang-jwt/jwt/v5" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) type headerCarrier http.Header func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key) } func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) } func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) } // Keys lists the keys stored in this carrier. func (hc headerCarrier) Keys() []string { keys := make([]string, 0, len(hc)) for k := range http.Header(hc) { keys = append(keys, k) } return keys } // Values returns a slice value associated with the passed key. func (hc headerCarrier) Values(key string) []string { return http.Header(hc).Values(key) } func newTokenHeader(headerKey string, token string) *headerCarrier { header := &headerCarrier{} header.Set(headerKey, token) return header } type Transport struct { kind transport.Kind endpoint string operation string reqHeader transport.Header } func (tr *Transport) Kind() transport.Kind { return tr.kind } func (tr *Transport) Endpoint() string { return tr.endpoint } func (tr *Transport) Operation() string { return tr.operation } func (tr *Transport) RequestHeader() transport.Header { return tr.reqHeader } func (tr *Transport) ReplyHeader() transport.Header { return nil } type CustomerClaims struct { Name string `json:"name"` jwt.RegisteredClaims } func TestJWTServerParse(t *testing.T) { var ( errConcurrentWrite = errors.New("concurrent write claims") errParseClaims = errors.New("bad result, token claims is not CustomerClaims") ) testKey := "testKey" tests := []struct { name string token func() string claims func() jwt.Claims exceptErr error key string goroutineNum int }{ { name: "normal", token: func() string { token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &CustomerClaims{}).SignedString([]byte(testKey)) if err != nil { panic(err) } return fmt.Sprintf(bearerFormat, token) }, claims: func() jwt.Claims { return &CustomerClaims{} }, exceptErr: nil, key: testKey, goroutineNum: 1, }, { name: "concurrent request", token: func() string { token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &CustomerClaims{ Name: strconv.Itoa(rand.Int()), }).SignedString([]byte(testKey)) if err != nil { panic(err) } return fmt.Sprintf(bearerFormat, token) }, claims: func() jwt.Claims { return &CustomerClaims{} }, exceptErr: nil, key: testKey, goroutineNum: 10000, }, } next := func(ctx context.Context, _ any) (any, error) { testToken, _ := FromContext(ctx) var name string if customerClaims, ok := testToken.(*CustomerClaims); ok { name = customerClaims.Name } else { return nil, errParseClaims } // mock biz time.Sleep(100 * time.Millisecond) if customerClaims, ok := testToken.(*CustomerClaims); ok { if name != customerClaims.Name { return nil, errConcurrentWrite } } else { return nil, errParseClaims } return "reply", nil } for _, test := range tests { t.Run(test.name, func(t *testing.T) { server := Server( func(*jwt.Token) (any, error) { return []byte(testKey), nil }, WithClaims(test.claims), )(next) wg := sync.WaitGroup{} for i := 0; i < test.goroutineNum; i++ { wg.Add(1) go func() { defer wg.Done() ctx := transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, test.token())}) _, err2 := server(ctx, test.name) if !errors.Is(test.exceptErr, err2) { t.Errorf("except error %v, but got %v", test.exceptErr, err2) } }() } wg.Wait() }) } } func TestServer(t *testing.T) { testKey := "testKey" mapClaims := jwt.MapClaims{} mapClaims["name"] = "xiaoli" claims := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } token = fmt.Sprintf(bearerFormat, token) tests := []struct { name string ctx context.Context signingMethod jwt.SigningMethod exceptErr error key string }{ { name: "normal", ctx: transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, token)}), signingMethod: jwt.SigningMethodHS256, exceptErr: nil, key: testKey, }, { name: "miss token", ctx: transport.NewServerContext(context.Background(), &Transport{reqHeader: headerCarrier{}}), signingMethod: jwt.SigningMethodHS256, exceptErr: ErrMissingJwtToken, key: testKey, }, { name: "token invalid", ctx: transport.NewServerContext(context.Background(), &Transport{ reqHeader: newTokenHeader(authorizationKey, fmt.Sprintf(bearerFormat, "12313123")), }), signingMethod: jwt.SigningMethodHS256, exceptErr: ErrTokenInvalid, key: testKey, }, { name: "method invalid", ctx: transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, token)}), signingMethod: jwt.SigningMethodES384, exceptErr: ErrUnSupportSigningMethod, key: testKey, }, { name: "miss signing method", ctx: transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, token)}), signingMethod: nil, exceptErr: nil, key: testKey, }, { name: "miss signing method", ctx: transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, token)}), signingMethod: nil, exceptErr: nil, key: testKey, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var testToken jwt.Claims next := func(ctx context.Context, req any) (any, error) { t.Log(req) testToken, _ = FromContext(ctx) return "reply", nil } var server middleware.Handler if test.signingMethod != nil { server = Server(func(*jwt.Token) (any, error) { return []byte(test.key), nil }, WithSigningMethod(test.signingMethod))(next) } else { server = Server(func(*jwt.Token) (any, error) { return []byte(test.key), nil })(next) } _, err2 := server(test.ctx, test.name) if !errors.Is(test.exceptErr, err2) { t.Errorf("except error %v, but got %v", test.exceptErr, err2) } if test.exceptErr == nil { if testToken == nil { t.Fatal("except testToken not nil, but got nil") } _, ok := testToken.(jwt.MapClaims) if !ok { t.Errorf("except testToken is jwt.MapClaims, but got %T", testToken) } } }) } } func TestClient(t *testing.T) { testKey := "testKey" claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{}) token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } tProvider := func(*jwt.Token) (any, error) { return []byte(testKey), nil } tests := []struct { name string expectError error tokenProvider jwt.Keyfunc }{ { name: "normal", expectError: nil, tokenProvider: tProvider, }, { name: "miss token provider", expectError: ErrNeedTokenProvider, tokenProvider: nil, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { next := func(context.Context, any) (any, error) { return "reply", nil } handler := Client(test.tokenProvider)(next) header := &headerCarrier{} _, err2 := handler(transport.NewClientContext(context.Background(), &Transport{reqHeader: header}), "ok") if !errors.Is(test.expectError, err2) { t.Errorf("except error %v, but got %v", test.expectError, err2) } if err2 == nil { if !reflect.DeepEqual(header.Get(authorizationKey), fmt.Sprintf(bearerFormat, token)) { t.Errorf("except header %s, but got %s", fmt.Sprintf(bearerFormat, token), header.Get(authorizationKey)) } } }) } } func TestTokenExpire(t *testing.T) { testKey := "testKey" claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Millisecond)), }) token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } token = fmt.Sprintf(bearerFormat, token) time.Sleep(time.Second) next := func(_ context.Context, req any) (any, error) { t.Log(req) return "reply", nil } ctx := transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, token)}) server := Server(func(*jwt.Token) (any, error) { return []byte(testKey), nil }, WithSigningMethod(jwt.SigningMethodHS256))(next) _, err2 := server(ctx, "test expire token") if !errors.Is(ErrTokenExpired, err2) { t.Errorf("except error %v, but got %v", ErrTokenExpired, err2) } } func TestMissingKeyFunc(t *testing.T) { testKey := "testKey" mapClaims := jwt.MapClaims{} mapClaims["name"] = "xiaoli" claims := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } token = fmt.Sprintf(bearerFormat, token) test := struct { name string ctx context.Context signingMethod jwt.SigningMethod exceptErr error key string }{ name: "miss key", ctx: transport.NewServerContext(context.Background(), &Transport{reqHeader: newTokenHeader(authorizationKey, token)}), signingMethod: jwt.SigningMethodHS256, exceptErr: ErrMissingKeyFunc, key: "", } var testToken jwt.Claims next := func(ctx context.Context, req any) (any, error) { t.Log(req) testToken, _ = FromContext(ctx) return "reply", nil } server := Server(nil)(next) _, err2 := server(test.ctx, test.name) if !errors.Is(test.exceptErr, err2) { t.Errorf("except error %v, but got %v", test.exceptErr, err2) } if test.exceptErr == nil { if testToken == nil { t.Errorf("except testToken not nil, but got nil") } } } func TestClientWithClaims(t *testing.T) { testKey := "testKey" mapClaims := jwt.MapClaims{} mapClaims["name"] = "xiaoli" mapClaimsFunc := func() jwt.Claims { return mapClaims } claims := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } tProvider := func(*jwt.Token) (any, error) { return []byte(testKey), nil } test := struct { name string expectError error tokenProvider jwt.Keyfunc }{ name: "normal", expectError: nil, tokenProvider: tProvider, } t.Run(test.name, func(t *testing.T) { next := func(context.Context, any) (any, error) { return "reply", nil } handler := Client(test.tokenProvider, WithClaims(mapClaimsFunc))(next) header := &headerCarrier{} _, err2 := handler(transport.NewClientContext(context.Background(), &Transport{reqHeader: header}), "ok") if !errors.Is(test.expectError, err2) { t.Errorf("except error %v, but got %v", test.expectError, err2) } if err2 == nil { if !reflect.DeepEqual(header.Get(authorizationKey), fmt.Sprintf(bearerFormat, token)) { t.Errorf("except header %s, but got %s", fmt.Sprintf(bearerFormat, token), header.Get(authorizationKey)) } } }) } func TestClientWithHeader(t *testing.T) { testKey := "testKey" mapClaims := jwt.MapClaims{} mapClaims["name"] = "xiaoli" mapClaimsFunc := func() jwt.Claims { return mapClaims } tokenHeader := map[string]any{ "test": "test", } claims := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) for k, v := range tokenHeader { claims.Header[k] = v } token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } tProvider := func(*jwt.Token) (any, error) { return []byte(testKey), nil } next := func(context.Context, any) (any, error) { return "reply", nil } handler := Client(tProvider, WithClaims(mapClaimsFunc), WithTokenHeader(tokenHeader))(next) header := &headerCarrier{} _, err2 := handler(transport.NewClientContext(context.Background(), &Transport{reqHeader: header}), "ok") if err2 != nil { t.Errorf("except error nil, but got %v", err2) } if !reflect.DeepEqual(header.Get(authorizationKey), fmt.Sprintf(bearerFormat, token)) { t.Errorf("except header %s, but got %s", fmt.Sprintf(bearerFormat, token), header.Get(authorizationKey)) } } func TestClientMissKey(t *testing.T) { testKey := "testKey" mapClaims := jwt.MapClaims{} mapClaims["name"] = "xiaoli" mapClaimsFunc := func() jwt.Claims { return mapClaims } claims := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) token, err := claims.SignedString([]byte(testKey)) if err != nil { panic(err) } tProvider := func(*jwt.Token) (any, error) { return nil, errors.New("some error") } test := struct { name string expectError error tokenProvider jwt.Keyfunc }{ name: "normal", expectError: ErrGetKey, tokenProvider: tProvider, } t.Run(test.name, func(t *testing.T) { next := func(context.Context, any) (any, error) { return "reply", nil } handler := Client(test.tokenProvider, WithClaims(mapClaimsFunc))(next) header := &headerCarrier{} _, err2 := handler(transport.NewClientContext(context.Background(), &Transport{reqHeader: header}), "ok") if !errors.Is(test.expectError, err2) { t.Errorf("except error %v, but got %v", test.expectError, err2) } if err2 == nil { if !reflect.DeepEqual(header.Get(authorizationKey), fmt.Sprintf(bearerFormat, token)) { t.Errorf("except header %s, but got %s", fmt.Sprintf(bearerFormat, token), header.Get(authorizationKey)) } } }) } func TestNewContextAndFromContext(t *testing.T) { tests := []struct { name string claims jwt.MapClaims }{ {"val not nil", jwt.MapClaims{"name": "kratos"}}, {"val nil", nil}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ctx := NewContext(context.Background(), test.claims) claims, ok := FromContext(ctx) if !ok { t.Fatal("ctx not found authKey{}") } if !reflect.DeepEqual(test.claims, claims) { t.Errorf(`want: %s, got: %v`, test.claims, claims) } }) } } func TestWithParserOptions(t *testing.T) { testKey := "testKey" issuer := "https://example.com" subject := "user" next := func(ctx context.Context, _ any) (any, error) { testToken, _ := FromContext(ctx) if testToken == nil { t.Error("expected testToken not nil, but got nil") } return "reply", nil } tests := []struct { name string claims jwt.RegisteredClaims parserOptions []jwt.ParserOption customClaims func() jwt.Claims exceptErr error }{ { name: "valid token with matching issuer", claims: jwt.RegisteredClaims{ Issuer: issuer, ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithIssuer(issuer), }, exceptErr: nil, }, { name: "invalid token with wrong issuer", claims: jwt.RegisteredClaims{ Issuer: "https://wrong-issuer.com", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithIssuer(issuer), }, exceptErr: ErrTokenParseFail, }, { name: "valid token with matching subject", claims: jwt.RegisteredClaims{ Subject: subject, ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithSubject(subject), }, exceptErr: nil, }, { name: "invalid token with wrong subject", claims: jwt.RegisteredClaims{ Subject: "admin", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithSubject(subject), }, exceptErr: ErrTokenParseFail, }, { name: "valid token with multiple parser options", claims: jwt.RegisteredClaims{ Issuer: issuer, Subject: subject, ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithIssuer(issuer), jwt.WithSubject(subject), jwt.WithExpirationRequired(), }, exceptErr: nil, }, { name: "invalid token missing required expiration", claims: jwt.RegisteredClaims{ Issuer: issuer, Subject: subject, }, parserOptions: []jwt.ParserOption{ jwt.WithExpirationRequired(), }, exceptErr: ErrTokenParseFail, }, { name: "valid token with no parser options (backward compatibility)", claims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: nil, exceptErr: nil, }, { name: "valid token with custom claims and matching issuer", claims: jwt.RegisteredClaims{ Issuer: issuer, ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithIssuer(issuer), }, customClaims: func() jwt.Claims { return &CustomerClaims{} }, exceptErr: nil, }, { name: "invalid token with custom claims and wrong issuer", claims: jwt.RegisteredClaims{ Issuer: "https://wrong-issuer.com", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, parserOptions: []jwt.ParserOption{ jwt.WithIssuer(issuer), }, customClaims: func() jwt.Claims { return &CustomerClaims{} }, exceptErr: ErrTokenParseFail, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, test.claims).SignedString([]byte(testKey)) if err != nil { t.Fatal(err) } ctx := transport.NewServerContext(context.Background(), &Transport{ reqHeader: newTokenHeader(authorizationKey, fmt.Sprintf(bearerFormat, token)), }) opts := []Option{WithSigningMethod(jwt.SigningMethodHS256)} if test.parserOptions != nil { opts = append(opts, WithParserOptions(test.parserOptions...)) } if test.customClaims != nil { opts = append(opts, WithClaims(test.customClaims)) } server := Server( func(*jwt.Token) (any, error) { return []byte(testKey), nil }, opts..., )(next) _, err2 := server(ctx, test.name) if !errors.Is(test.exceptErr, err2) { t.Errorf("expected error %v, but got %v", test.exceptErr, err2) } }) } } func TestWithParserOptionsConcurrent(t *testing.T) { testKey := "testKey" issuer := "https://example.com" next := func(ctx context.Context, _ any) (any, error) { testToken, _ := FromContext(ctx) if testToken == nil { return nil, errors.New("expected testToken not nil, but got nil") } return "reply", nil } server := Server( func(*jwt.Token) (any, error) { return []byte(testKey), nil }, WithClaims(func() jwt.Claims { return &CustomerClaims{} }), WithParserOptions(jwt.WithIssuer(issuer), jwt.WithExpirationRequired()), )(next) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &CustomerClaims{ Name: strconv.Itoa(rand.Int()), RegisteredClaims: jwt.RegisteredClaims{ Issuer: issuer, ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, }).SignedString([]byte(testKey)) if err != nil { t.Error(err) return } ctx := transport.NewServerContext(context.Background(), &Transport{ reqHeader: newTokenHeader(authorizationKey, fmt.Sprintf(bearerFormat, token)), }) _, err2 := server(ctx, "concurrent") if err2 != nil { t.Errorf("expected nil error, but got %v", err2) } }() } wg.Wait() } func TestWithParserOptionsEmpty(t *testing.T) { testKey := "testKey" next := func(_ context.Context, _ any) (any, error) { return "reply", nil } token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "name": "kratos", }).SignedString([]byte(testKey)) if err != nil { t.Fatal(err) } ctx := transport.NewServerContext(context.Background(), &Transport{ reqHeader: newTokenHeader(authorizationKey, fmt.Sprintf(bearerFormat, token)), }) // WithParserOptions called with no arguments should behave identically to not calling it. server := Server( func(*jwt.Token) (any, error) { return []byte(testKey), nil }, WithParserOptions(), )(next) _, err2 := server(ctx, "empty parser options") if err2 != nil { t.Errorf("expected nil error, but got %v", err2) } } ================================================ FILE: middleware/circuitbreaker/circuitbreaker.go ================================================ package circuitbreaker import ( "context" "github.com/go-kratos/aegis/circuitbreaker" "github.com/go-kratos/aegis/circuitbreaker/sre" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/internal/group" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) // ErrNotAllowed is request failed due to circuit breaker triggered. var ErrNotAllowed = errors.New(503, "CIRCUITBREAKER", "request failed due to circuit breaker triggered") // Option is circuit breaker option. type Option func(*options) // WithGroup with circuit breaker group. // NOTE: implements generics circuitbreaker.CircuitBreaker func WithGroup(g *group.Group[circuitbreaker.CircuitBreaker]) Option { return func(o *options) { o.group = g } } // WithCircuitBreaker with circuit breaker genFunc. func WithCircuitBreaker(genBreakerFunc func() circuitbreaker.CircuitBreaker) Option { return func(o *options) { o.group = group.NewGroup(func() circuitbreaker.CircuitBreaker { return genBreakerFunc() }) } } type options struct { group *group.Group[circuitbreaker.CircuitBreaker] } // Client circuitbreaker middleware will return errBreakerTriggered when the circuit // breaker is triggered and the request is rejected directly. func Client(opts ...Option) middleware.Middleware { opt := &options{ group: group.NewGroup(func() circuitbreaker.CircuitBreaker { return sre.NewBreaker() }), } for _, o := range opts { o(opt) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (any, error) { info, _ := transport.FromClientContext(ctx) breaker := opt.group.Get(info.Operation()) if err := breaker.Allow(); err != nil { // rejected // NOTE: when client reject requests locally, // continue to add counter let the drop ratio higher. breaker.MarkFailed() return nil, ErrNotAllowed } // allowed reply, err := handler(ctx, req) if err != nil && (errors.IsInternalServer(err) || errors.IsServiceUnavailable(err) || errors.IsGatewayTimeout(err)) { breaker.MarkFailed() } else { breaker.MarkSuccess() } return reply, err } } } ================================================ FILE: middleware/circuitbreaker/circuitbreaker_test.go ================================================ package circuitbreaker import ( "context" "errors" "testing" "github.com/go-kratos/aegis/circuitbreaker" kratoserrors "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/internal/group" "github.com/go-kratos/kratos/v2/transport" ) type transportMock struct { kind transport.Kind endpoint string operation string } type circuitBreakerMock struct { err error } func (tr *transportMock) Kind() transport.Kind { return tr.kind } func (tr *transportMock) Endpoint() string { return tr.endpoint } func (tr *transportMock) Operation() string { return tr.operation } func (tr *transportMock) RequestHeader() transport.Header { return nil } func (tr *transportMock) ReplyHeader() transport.Header { return nil } func (c *circuitBreakerMock) Allow() error { return c.err } func (c *circuitBreakerMock) MarkSuccess() {} func (c *circuitBreakerMock) MarkFailed() {} func Test_WithGroup(t *testing.T) { o := options{ group: group.NewGroup(func() circuitbreaker.CircuitBreaker { return &circuitBreakerMock{} }), } WithGroup(nil)(&o) if o.group != nil { t.Error("The group property must be updated to nil.") } } func TestServer(_ *testing.T) { nextValid := func(context.Context, any) (any, error) { return "Hello valid", nil } nextInvalid := func(context.Context, any) (any, error) { return nil, kratoserrors.InternalServer("", "") } ctx := transport.NewClientContext(context.Background(), &transportMock{}) _, _ = Client(func(o *options) { o.group = group.NewGroup(func() circuitbreaker.CircuitBreaker { return &circuitBreakerMock{err: errors.New("circuitbreaker error")} }) })(nextValid)(ctx, nil) _, _ = Client(func(_ *options) {})(nextValid)(ctx, nil) _, _ = Client(func(_ *options) {})(nextInvalid)(ctx, nil) } ================================================ FILE: middleware/logging/logging.go ================================================ package logging import ( "context" "fmt" "time" "google.golang.org/grpc/codes" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http/status" ) // Redacter defines how to log an object type Redacter interface { Redact() string } // Server is an server logging middleware. func Server(logger log.Logger) middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { var ( code int32 reason string kind string operation string ) // default code code = int32(status.FromGRPCCode(codes.OK)) startTime := time.Now() if info, ok := transport.FromServerContext(ctx); ok { kind = info.Kind().String() operation = info.Operation() } reply, err = handler(ctx, req) if se := errors.FromError(err); se != nil { code = se.Code reason = se.Reason } level, stack := extractError(err) log.NewHelper(log.WithContext(ctx, logger)).Log(level, "kind", "server", "component", kind, "operation", operation, "args", extractArgs(req), "code", code, "reason", reason, "stack", stack, "latency", time.Since(startTime).Seconds(), ) return } } } // Client is a client logging middleware. func Client(logger log.Logger) middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { var ( code int32 reason string kind string operation string ) // default code code = int32(status.FromGRPCCode(codes.OK)) startTime := time.Now() if info, ok := transport.FromClientContext(ctx); ok { kind = info.Kind().String() operation = info.Operation() } reply, err = handler(ctx, req) if se := errors.FromError(err); se != nil { code = se.Code reason = se.Reason } level, stack := extractError(err) log.NewHelper(log.WithContext(ctx, logger)).Log(level, "kind", "client", "component", kind, "operation", operation, "args", extractArgs(req), "code", code, "reason", reason, "stack", stack, "latency", time.Since(startTime).Seconds(), ) return } } } // extractArgs returns the string of the req func extractArgs(req any) string { if redacter, ok := req.(Redacter); ok { return redacter.Redact() } if stringer, ok := req.(fmt.Stringer); ok { return stringer.String() } return fmt.Sprintf("%+v", req) } // extractError returns the string of the error func extractError(err error) (log.Level, string) { if err != nil { return log.LevelError, fmt.Sprintf("%+v", err) } return log.LevelInfo, "" } ================================================ FILE: middleware/logging/logging_test.go ================================================ package logging import ( "bytes" "context" "errors" "testing" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) var _ transport.Transporter = (*Transport)(nil) type Transport struct { kind transport.Kind endpoint string operation string } func (tr *Transport) Kind() transport.Kind { return tr.kind } func (tr *Transport) Endpoint() string { return tr.endpoint } func (tr *Transport) Operation() string { return tr.operation } func (tr *Transport) RequestHeader() transport.Header { return nil } func (tr *Transport) ReplyHeader() transport.Header { return nil } func TestHTTP(t *testing.T) { err := errors.New("reply.error") bf := bytes.NewBuffer(nil) logger := log.NewStdLogger(bf) tests := []struct { name string kind func(logger log.Logger) middleware.Middleware err error ctx context.Context }{ { "http-server@fail", Server, err, func() context.Context { return transport.NewServerContext(context.Background(), &Transport{kind: transport.KindHTTP, endpoint: "endpoint", operation: "/package.service/method"}) }(), }, { "http-server@succ", Server, nil, func() context.Context { return transport.NewServerContext(context.Background(), &Transport{kind: transport.KindHTTP, endpoint: "endpoint", operation: "/package.service/method"}) }(), }, { "http-client@succ", Client, nil, func() context.Context { return transport.NewClientContext(context.Background(), &Transport{kind: transport.KindHTTP, endpoint: "endpoint", operation: "/package.service/method"}) }(), }, { "http-client@fail", Client, err, func() context.Context { return transport.NewClientContext(context.Background(), &Transport{kind: transport.KindHTTP, endpoint: "endpoint", operation: "/package.service/method"}) }(), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { bf.Reset() next := func(context.Context, any) (any, error) { return "reply", test.err } next = test.kind(logger)(next) v, e := next(test.ctx, "req.args") t.Logf("[%s]reply: %v, error: %v", test.name, v, e) t.Logf("[%s]log:%s", test.name, bf.String()) }) } } type ( dummy struct { field string } dummyStringer struct { field string } dummyStringerRedacter struct { field string } ) func (d *dummyStringer) String() string { return "my value" } func (d *dummyStringerRedacter) String() string { return "my value" } func (d *dummyStringerRedacter) Redact() string { return "my value redacted" } func TestExtractArgs(t *testing.T) { tests := []struct { name string req any expected string }{ { name: "dummyStringer", req: &dummyStringer{field: ""}, expected: "my value", }, { name: "dummy", req: &dummy{field: "value"}, expected: "&{field:value}", }, { name: "dummyStringerRedacter", req: &dummyStringerRedacter{field: ""}, expected: "my value redacted", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if value := extractArgs(test.req); value != test.expected { t.Errorf(`The stringified %s structure must be equal to "%s", %v given`, test.name, test.expected, value) } }) } } func TestExtractError(t *testing.T) { tests := []struct { name string err error wantLevel log.Level wantErrStr string }{ { "no error", nil, log.LevelInfo, "", }, { "error", errors.New("test error"), log.LevelError, "test error", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { level, errStr := extractError(test.err) if level != test.wantLevel { t.Errorf("want: %d, got: %d", test.wantLevel, level) } if errStr != test.wantErrStr { t.Errorf("want: %s, got: %s", test.wantErrStr, errStr) } }) } } type extractKeyValues [][]any func (l *extractKeyValues) Log(_ log.Level, kv ...any) error { *l = append(*l, kv); return nil } func TestServer_CallerPath(t *testing.T) { var a extractKeyValues logger := log.With(&a, "caller", log.Caller(5)) // report where the helper was called // make sure the caller is same sameCaller := func(fn middleware.Handler) { _, _ = fn(context.Background(), nil) } // caller: [... log inside middleware, fn(context.Background(), nil)] h := func(context.Context, any) (a any, e error) { return } h = Server(logger)(h) sameCaller(h) // caller: [... helper.Info("foo"), fn(context.Background(), nil)] helper := log.NewHelper(logger) sameCaller(func(context.Context, any) (a any, e error) { helper.Info("foo"); return }) t.Log(a[0]) t.Log(a[1]) if a[0][0] != "caller" || a[1][0] != "caller" { t.Fatal("caller not found") } if a[0][1] != a[1][1] { t.Fatalf("middleware should have the same caller as log.Helper. middleware: %s, helper: %s", a[0][1], a[1][1]) } } func TestClient_CallerPath(t *testing.T) { var a extractKeyValues logger := log.With(&a, "caller", log.Caller(5)) // report where the helper was called // make sure the caller is same sameCaller := func(fn middleware.Handler) { _, _ = fn(context.Background(), nil) } // caller: [... log inside middleware, fn(context.Background(), nil)] h := func(context.Context, any) (a any, e error) { return } h = Client(logger)(h) sameCaller(h) // caller: [... helper.Info("foo"), fn(context.Background(), nil)] helper := log.NewHelper(logger) sameCaller(func(context.Context, any) (a any, e error) { helper.Info("foo"); return }) t.Log(a[0]) t.Log(a[1]) if a[0][0] != "caller" || a[1][0] != "caller" { t.Fatal("caller not found") } if a[0][1] != a[1][1] { t.Fatalf("middleware should have the same caller as log.Helper. middleware: %s, helper: %s", a[0][1], a[1][1]) } } ================================================ FILE: middleware/metadata/metadata.go ================================================ package metadata import ( "context" "strings" "github.com/go-kratos/kratos/v2/metadata" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) // Option is metadata option. type Option func(*options) type options struct { prefix []string md metadata.Metadata } func (o *options) hasPrefix(key string) bool { k := strings.ToLower(key) for _, prefix := range o.prefix { if strings.HasPrefix(k, prefix) { return true } } return false } // WithConstants with constant metadata key value. func WithConstants(md metadata.Metadata) Option { return func(o *options) { o.md = md } } // WithPropagatedPrefix with propagated key prefix. func WithPropagatedPrefix(prefix ...string) Option { return func(o *options) { o.prefix = prefix } } // Server is middleware server-side metadata. func Server(opts ...Option) middleware.Middleware { options := &options{ prefix: []string{"x-md-"}, // x-md-global-, x-md-local } for _, o := range opts { o(options) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { tr, ok := transport.FromServerContext(ctx) if !ok { return handler(ctx, req) } md := options.md.Clone() header := tr.RequestHeader() for _, k := range header.Keys() { if options.hasPrefix(k) { for _, v := range header.Values(k) { md.Add(k, v) } } } ctx = metadata.NewServerContext(ctx, md) return handler(ctx, req) } } } // Client is middleware client-side metadata. func Client(opts ...Option) middleware.Middleware { options := &options{ prefix: []string{"x-md-global-"}, } for _, o := range opts { o(options) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { tr, ok := transport.FromClientContext(ctx) if !ok { return handler(ctx, req) } header := tr.RequestHeader() // x-md-local- for k, vList := range options.md { for _, v := range vList { header.Add(k, v) } } if md, ok := metadata.FromClientContext(ctx); ok { for k, vList := range md { for _, v := range vList { header.Add(k, v) } } } // x-md-global- if md, ok := metadata.FromServerContext(ctx); ok { for k, vList := range md { if options.hasPrefix(k) { for _, v := range vList { header.Add(k, v) } } } } return handler(ctx, req) } } } ================================================ FILE: middleware/metadata/metadata_test.go ================================================ package metadata import ( "context" "errors" "net/http" "reflect" "testing" "github.com/go-kratos/kratos/v2/metadata" "github.com/go-kratos/kratos/v2/transport" ) type headerCarrier http.Header func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key) } func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) } func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) } // Keys lists the keys stored in this carrier. func (hc headerCarrier) Keys() []string { keys := make([]string, 0, len(hc)) for k := range http.Header(hc) { keys = append(keys, k) } return keys } // Values returns a slice value associated with the passed key. func (hc headerCarrier) Values(key string) []string { return http.Header(hc).Values(key) } type testTransport struct{ header headerCarrier } func (tr *testTransport) Kind() transport.Kind { return transport.KindHTTP } func (tr *testTransport) Endpoint() string { return "" } func (tr *testTransport) Operation() string { return "" } func (tr *testTransport) RequestHeader() transport.Header { return tr.header } func (tr *testTransport) ReplyHeader() transport.Header { return tr.header } var ( globalKey = "x-md-global-key" globalValue = "global-value" localKey = "x-md-local-key" localValue = "local-value" customKey = "x-md-local-custom" customValue = "custom-value" constKey = "x-md-local-const" constValue = "x-md-local-const" ) func TestSever(t *testing.T) { hs := func(ctx context.Context, in any) (any, error) { md, ok := metadata.FromServerContext(ctx) if !ok { return nil, errors.New("no md") } if md.Get(constKey) != constValue { return nil, errors.New("const not equal") } if md.Get(globalKey) != globalValue { return nil, errors.New("global not equal") } if md.Get(localKey) != localValue { return nil, errors.New("local not equal") } return in, nil } hc := headerCarrier{} hc.Set(globalKey, globalValue) hc.Set(localKey, localValue) ctx := transport.NewServerContext(context.Background(), &testTransport{hc}) // const md constMD := metadata.New() constMD.Set(constKey, constValue) reply, err := Server(WithConstants(constMD))(hs)(ctx, "foo") if err != nil { t.Fatal(err) } if reply.(string) != "foo" { t.Fatalf("want foo got %v", reply) } } func TestClient(t *testing.T) { hs := func(ctx context.Context, in any) (any, error) { tr, ok := transport.FromClientContext(ctx) if !ok { return nil, errors.New("no md") } if tr.RequestHeader().Get(constKey) != constValue { return nil, errors.New("const not equal") } if tr.RequestHeader().Get(customKey) != customValue { return nil, errors.New("custom not equal") } if tr.RequestHeader().Get(globalKey) != globalValue { return nil, errors.New("global not equal") } if tr.RequestHeader().Get(localKey) != "" { return nil, errors.New("local must empty") } return in, nil } // server md serverMD := metadata.New() serverMD.Set(globalKey, globalValue) serverMD.Set(localKey, localValue) ctx := metadata.NewServerContext(context.Background(), serverMD) // client md clientMD := metadata.New() clientMD.Set(customKey, customValue) ctx = metadata.NewClientContext(ctx, clientMD) // transport carrier ctx = transport.NewClientContext(ctx, &testTransport{headerCarrier{}}) // const md constMD := metadata.New() constMD.Set(constKey, constValue) reply, err := Client(WithConstants(constMD))(hs)(ctx, "bar") if err != nil { t.Fatal(err) } if reply.(string) != "bar" { t.Fatalf("want foo got %v", reply) } } func TestWithConstants(t *testing.T) { md := metadata.Metadata{ constKey: {constValue}, } options := &options{ md: metadata.Metadata{ "override": {"override"}, }, } WithConstants(md)(options) if !reflect.DeepEqual(md, options.md) { t.Errorf("want: %v, got: %v", md, options.md) } } func TestOptions_WithPropagatedPrefix(t *testing.T) { options := &options{ prefix: []string{"override"}, } prefixes := []string{"something", "another"} WithPropagatedPrefix(prefixes...)(options) if !reflect.DeepEqual(prefixes, options.prefix) { t.Error("The prefix must be overridden.") } } func TestOptions_hasPrefix(t *testing.T) { tests := []struct { name string options *options key string exists bool }{ {"exists key upper", &options{prefix: []string{"prefix"}}, "PREFIX_true", true}, {"exists key lower", &options{prefix: []string{"prefix"}}, "prefix_true", true}, {"not exists key upper", &options{prefix: []string{"prefix"}}, "false_PREFIX", false}, {"not exists key lower", &options{prefix: []string{"prefix"}}, "false_prefix", false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { exists := test.options.hasPrefix(test.key) if test.exists != exists { t.Errorf("key: '%sr', not exists prefixs: %v", test.key, test.options.prefix) } }) } } ================================================ FILE: middleware/metrics/metrics.go ================================================ package metrics import ( "context" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" metricsdk "go.opentelemetry.io/otel/sdk/metric" "google.golang.org/grpc/codes" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http/status" ) const ( metricLabelKind = "kind" metricLabelOperation = "operation" metricLabelCode = "code" metricLabelReason = "reason" ) const ( DefaultServerSecondsHistogramName = "server_requests_seconds" DefaultServerRequestsCounterName = "server_requests_code_total" DefaultClientSecondsHistogramName = "client_requests_seconds" DefaultClientRequestsCounterName = "client_requests_code_total" ) // Option is metrics option. type Option func(*options) // WithRequests with requests counter. func WithRequests(c metric.Int64Counter) Option { return func(o *options) { o.requests = c } } // WithSeconds with seconds histogram. // notice: the record unit in current middleware is s(Seconds) func WithSeconds(histogram metric.Float64Histogram) Option { return func(o *options) { o.seconds = histogram } } // DefaultRequestsCounter // return metric.Int64Counter for WithRequests // suggest histogramName = _requests_code_total func DefaultRequestsCounter(meter metric.Meter, histogramName string) (metric.Int64Counter, error) { return meter.Int64Counter(histogramName, metric.WithUnit("{call}")) } // DefaultSecondsHistogram // return metric.Float64Histogram for WithSeconds // suggest histogramName = _requests_seconds func DefaultSecondsHistogram(meter metric.Meter, histogramName string) (metric.Float64Histogram, error) { return meter.Float64Histogram( histogramName, metric.WithUnit("s"), metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.1, 0.250, 0.5, 1), ) } // DefaultSecondsHistogramView // need register in sdkmetric.MeterProvider // eg: // view := SecondsHistogramView() // mp := sdkmetric.NewMeterProvider(sdkmetric.WithView(view)) // otel.SetMeterProvider(mp) func DefaultSecondsHistogramView(histogramName string) metricsdk.View { return func(instrument metricsdk.Instrument) (metricsdk.Stream, bool) { if instrument.Name == histogramName { return metricsdk.Stream{ Name: instrument.Name, Description: instrument.Description, Unit: instrument.Unit, Aggregation: metricsdk.AggregationExplicitBucketHistogram{ Boundaries: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.250, 0.5, 1}, NoMinMax: true, }, AttributeFilter: func(attribute.KeyValue) bool { return true }, }, true } return metricsdk.Stream{}, false } } type options struct { // counter: _requests_code_total{kind, operation, code, reason} requests metric.Int64Counter // histogram: _requests_seconds{kind, operation} seconds metric.Float64Histogram } // Server is middleware server-side metrics. func Server(opts ...Option) middleware.Middleware { op := options{} for _, o := range opts { o(&op) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (any, error) { // if requests and seconds are nil, return directly if op.requests == nil && op.seconds == nil { return handler(ctx, req) } var ( code int reason string kind string operation string ) // default code code = status.FromGRPCCode(codes.OK) startTime := time.Now() if info, ok := transport.FromServerContext(ctx); ok { kind = info.Kind().String() operation = info.Operation() } reply, err := handler(ctx, req) if se := errors.FromError(err); se != nil { code = int(se.Code) reason = se.Reason } if op.requests != nil { op.requests.Add( ctx, 1, metric.WithAttributes( attribute.String(metricLabelKind, kind), attribute.String(metricLabelOperation, operation), attribute.Int(metricLabelCode, code), attribute.String(metricLabelReason, reason), ), ) } if op.seconds != nil { op.seconds.Record( ctx, time.Since(startTime).Seconds(), metric.WithAttributes( attribute.String(metricLabelKind, kind), attribute.String(metricLabelOperation, operation), ), ) } return reply, err } } } // Client is middleware client-side metrics. func Client(opts ...Option) middleware.Middleware { op := options{} for _, o := range opts { o(&op) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (any, error) { var ( code int reason string kind string operation string ) // default code code = status.FromGRPCCode(codes.OK) startTime := time.Now() if info, ok := transport.FromClientContext(ctx); ok { kind = info.Kind().String() operation = info.Operation() } reply, err := handler(ctx, req) if se := errors.FromError(err); se != nil { code = int(se.Code) reason = se.Reason } if op.requests != nil { op.requests.Add( ctx, 1, metric.WithAttributes( attribute.String(metricLabelKind, kind), attribute.String(metricLabelOperation, operation), attribute.Int(metricLabelCode, code), attribute.String(metricLabelReason, reason), ), ) } if op.seconds != nil { op.seconds.Record( ctx, time.Since(startTime).Seconds(), metric.WithAttributes( attribute.String(metricLabelKind, kind), attribute.String(metricLabelOperation, operation), ), ) } return reply, err } } } ================================================ FILE: middleware/metrics/metrics_test.go ================================================ package metrics import ( "bytes" "context" "encoding/json" "errors" "fmt" "math/rand/v2" "strings" "sync" "testing" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" sdktrace "go.opentelemetry.io/otel/sdk/trace" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http" ) type dummyExporter struct { mu sync.Mutex writeBuf bytes.Buffer } func (x *dummyExporter) Temporality(kind sdkmetric.InstrumentKind) metricdata.Temporality { return sdkmetric.DefaultTemporalitySelector(kind) } func (x *dummyExporter) Aggregation(kind sdkmetric.InstrumentKind) sdkmetric.Aggregation { return sdkmetric.DefaultAggregationSelector(kind) } func (x *dummyExporter) Export(ctx context.Context, resourceMetrics *metricdata.ResourceMetrics) error { select { case <-ctx.Done(): // Don't do anything if the context has already timed out. return ctx.Err() default: // Context is still valid, continue. } x.mu.Lock() defer x.mu.Unlock() return json.NewEncoder(&x.writeBuf).Encode(resourceMetrics) } func (x *dummyExporter) ForceFlush(ctx context.Context) error { return ctx.Err() } func (x *dummyExporter) Shutdown(ctx context.Context) error { return ctx.Err() } func (x *dummyExporter) String() string { x.mu.Lock() defer x.mu.Unlock() return x.writeBuf.String() } var exporter = &dummyExporter{} func init() { err := EnableOTELExemplar() if err != nil { panic(err) } mp := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exporter, sdkmetric.WithInterval(time.Microsecond*400))), sdkmetric.WithView(DefaultSecondsHistogramView(DefaultServerSecondsHistogramName), func(instrument sdkmetric.Instrument) (sdkmetric.Stream, bool) { return sdkmetric.Stream{ Name: instrument.Name, Description: instrument.Description, Unit: instrument.Unit, AttributeFilter: func(attribute.KeyValue) bool { return true }, }, true }), ) otel.SetMeterProvider(mp) tr := sdktrace.NewTracerProvider() otel.SetTracerProvider(tr) } func TestWithRequests(t *testing.T) { o := options{} if o.requests != nil { t.Errorf(`The type of the option requests property must be of "nil"`) return } meter := otel.Meter("test_meter") requests, err := meter.Int64Counter(DefaultServerRequestsCounterName) if err != nil { t.Errorf("[Int64Counter] something went wrong: %v", err) return } WithRequests(requests)(&o) if o.requests == nil { t.Errorf(`The type of the option requests property must be of "mockCounter", %T given.`, o.requests) } } func TestWithSeconds(t *testing.T) { o := options{} if o.seconds != nil { t.Errorf(`The type of the option seconds property must be of "nil"`) return } meter := otel.Meter("test_meter") seconds, err := meter.Float64Histogram(DefaultServerSecondsHistogramName) if err != nil { t.Errorf("[Float64Histogram] something went wrong: %v", err) return } WithSeconds(seconds)(&o) if o.seconds == nil { t.Errorf(`The type of the option seconds property must be of "mockObserver", %T given.`, o.requests) } } func TestServer(t *testing.T) { tracer := otel.Tracer("test_trace") ctx, span := tracer.Start(context.Background(), "TestServer") defer span.End() e := errors.New("got an error") nextError := func(context.Context, any) (any, error) { return nil, e } nextValid := func(context.Context, any) (any, error) { time.Sleep(time.Millisecond * time.Duration(rand.Int32N(100))) return "Hello valid", nil } // init server handler meter := otel.Meter("test_meter") requests, err := DefaultRequestsCounter(meter, DefaultServerRequestsCounterName) if err != nil { t.Errorf("[DefaultRequestsCounter] something went wrong: %v", err) return } seconds, err := DefaultSecondsHistogram(meter, DefaultServerSecondsHistogramName) if err != nil { t.Errorf("[DefaultSecondsHistogram] something went wrong: %v", err) return } serverHandler := Server( WithRequests(requests), WithSeconds(seconds), ) _, err = serverHandler(nextError)(ctx, "test:") if err != e { t.Error("The given error mismatch the expected.") return } for i := 0; i < 20; i++ { res, err := serverHandler(nextValid)(transport.NewServerContext(ctx, &http.Transport{}), "test:") if err != nil { t.Error("The server must not throw an error.") } if res != "Hello valid" { t.Error(`The server must return a "Hello valid" response.`) } } bufStr := exporter.String() for _, label := range []string{ metricLabelKind, metricLabelOperation, metricLabelCode, metricLabelReason, "SpanID", "TraceID", } { if !strings.Contains(bufStr, fmt.Sprintf("\"%s\"", label)) { t.Errorf("The expected metric label %s is not found in the output: %s", label, bufStr) } } } func TestClient(t *testing.T) { tracer := otel.Tracer("test_trace") ctx, span := tracer.Start(context.Background(), "TestClient") defer span.End() e := errors.New("got an error") nextError := func(context.Context, any) (any, error) { return nil, e } nextValid := func(context.Context, any) (any, error) { return "Hello valid", nil } // init client handler meter := otel.Meter("test_meter") requests, err := DefaultRequestsCounter(meter, DefaultServerRequestsCounterName) if err != nil { t.Errorf("[DefaultRequestsCounter] something went wrong: %v", err) return } seconds, err := DefaultSecondsHistogram(meter, DefaultServerSecondsHistogramName) if err != nil { t.Errorf("[DefaultSecondsHistogram] something went wrong: %v", err) return } clientHandler := Client( WithRequests(requests), WithSeconds(seconds), ) _, err = clientHandler(nextError)(ctx, "test:") if err != e { t.Error("The given error mismatch the expected.") } for i := 0; i < 20; i++ { res, err := clientHandler(nextValid)(transport.NewClientContext(ctx, &http.Transport{}), "test:") if err != nil { t.Error("The server must not throw an error.") } if res != "Hello valid" { t.Error(`The server must return a "Hello valid" response.`) } } bufStr := exporter.String() for _, label := range []string{ metricLabelKind, metricLabelOperation, metricLabelCode, metricLabelReason, "SpanID", "TraceID", } { if !strings.Contains(bufStr, fmt.Sprintf("\"%s\"", label)) { t.Errorf("The expected metric label %s is not found in the output: %s", label, bufStr) } } } ================================================ FILE: middleware/metrics/otel.go ================================================ package metrics import "os" func EnableOTELExemplar() error { return os.Setenv("OTEL_GO_X_EXEMPLAR", "true") } ================================================ FILE: middleware/middleware.go ================================================ package middleware import ( "context" ) // Handler defines the handler invoked by Middleware. type Handler func(ctx context.Context, req any) (any, error) // Middleware is HTTP/gRPC transport middleware. type Middleware func(Handler) Handler // Chain returns a Middleware that specifies the chained handler for endpoint. func Chain(m ...Middleware) Middleware { return func(next Handler) Handler { for i := len(m) - 1; i >= 0; i-- { next = m[i](next) } return next } } ================================================ FILE: middleware/middleware_test.go ================================================ package middleware import ( "context" "fmt" "reflect" "testing" ) var i int func TestChain(t *testing.T) { next := func(_ context.Context, req any) (any, error) { if req != "hello kratos!" { t.Errorf("expect %v, got %v", "hello kratos!", req) } i += 10 return "reply", nil } got, err := Chain(test1Middleware, test2Middleware, test3Middleware)(next)(context.Background(), "hello kratos!") if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual(got, "reply") { t.Errorf("expect %v, got %v", "reply", got) } if !reflect.DeepEqual(i, 16) { t.Errorf("expect %v, got %v", 16, i) } } func test1Middleware(handler Handler) Handler { return func(ctx context.Context, req any) (reply any, err error) { fmt.Println("test1 before") i++ reply, err = handler(ctx, req) fmt.Println("test1 after") return } } func test2Middleware(handler Handler) Handler { return func(ctx context.Context, req any) (reply any, err error) { fmt.Println("test2 before") i += 2 reply, err = handler(ctx, req) fmt.Println("test2 after") return } } func test3Middleware(handler Handler) Handler { return func(ctx context.Context, req any) (reply any, err error) { fmt.Println("test3 before") i += 3 reply, err = handler(ctx, req) fmt.Println("test3 after") return } } ================================================ FILE: middleware/ratelimit/ratelimit.go ================================================ package ratelimit import ( "context" "github.com/go-kratos/aegis/ratelimit" "github.com/go-kratos/aegis/ratelimit/bbr" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" ) // ErrLimitExceed is service unavailable due to rate limit exceeded. var ErrLimitExceed = errors.New(429, "RATELIMIT", "service unavailable due to rate limit exceeded") // Option is ratelimit option. type Option func(*options) // WithLimiter set Limiter implementation, // default is bbr limiter func WithLimiter(limiter ratelimit.Limiter) Option { return func(o *options) { o.limiter = limiter } } type options struct { limiter ratelimit.Limiter } // Server ratelimiter middleware func Server(opts ...Option) middleware.Middleware { options := &options{ limiter: bbr.NewLimiter(), } for _, o := range opts { o(options) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { done, e := options.limiter.Allow() if e != nil { // rejected return nil, ErrLimitExceed } // allowed reply, err = handler(ctx, req) done(ratelimit.DoneInfo{Err: err}) return } } } ================================================ FILE: middleware/ratelimit/ratelimit_test.go ================================================ package ratelimit import ( "context" "errors" "testing" "github.com/go-kratos/aegis/ratelimit" ) type ( ratelimitMock struct { reached bool } ratelimitReachedMock struct { reached bool } ) func (r *ratelimitMock) Allow() (ratelimit.DoneFunc, error) { return func(_ ratelimit.DoneInfo) { r.reached = true }, nil } func (r *ratelimitReachedMock) Allow() (ratelimit.DoneFunc, error) { return func(_ ratelimit.DoneInfo) { r.reached = true }, errors.New("errored") } func Test_WithLimiter(t *testing.T) { o := options{ limiter: &ratelimitMock{}, } WithLimiter(nil)(&o) if o.limiter != nil { t.Error("The limiter property must be updated.") } } func TestServer(t *testing.T) { nextValid := func(context.Context, any) (any, error) { return "Hello valid", nil } rlm := &ratelimitMock{} rlrm := &ratelimitReachedMock{} _, _ = Server(func(o *options) { o.limiter = rlm })(nextValid)(context.Background(), nil) if !rlm.reached { t.Error("The ratelimit must run the done function.") } _, _ = Server(func(o *options) { o.limiter = rlrm })(nextValid)(context.Background(), nil) if rlrm.reached { t.Error("The ratelimit must not run the done function and should be denied.") } } ================================================ FILE: middleware/recovery/recovery.go ================================================ package recovery import ( "context" "runtime" "time" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" ) // Latency is recovery latency context key type Latency struct{} // ErrUnknownRequest is unknown request error. var ErrUnknownRequest = errors.InternalServer("UNKNOWN", "unknown request error") // HandlerFunc is recovery handler func. type HandlerFunc func(ctx context.Context, req, err any) error // Option is recovery option. type Option func(*options) type options struct { handler HandlerFunc } // WithHandler with recovery handler. func WithHandler(h HandlerFunc) Option { return func(o *options) { o.handler = h } } // Recovery is a server middleware that recovers from any panics. func Recovery(opts ...Option) middleware.Middleware { op := options{ handler: func(context.Context, any, any) error { return ErrUnknownRequest }, } for _, o := range opts { o(&op) } return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { startTime := time.Now() defer func() { if rerr := recover(); rerr != nil { buf := make([]byte, 64<<10) //nolint:mnd n := runtime.Stack(buf, false) buf = buf[:n] log.Context(ctx).Errorf("%v: %+v\n%s\n", rerr, req, buf) ctx = context.WithValue(ctx, Latency{}, time.Since(startTime).Seconds()) err = op.handler(ctx, req, rerr) } }() return handler(ctx, req) } } } ================================================ FILE: middleware/recovery/recovery_test.go ================================================ package recovery import ( "context" "fmt" "testing" "github.com/go-kratos/kratos/v2/errors" ) func TestOnce(t *testing.T) { defer func() { if recover() != nil { t.Error("fail") } }() next := func(context.Context, any) (any, error) { panic("panic reason") } _, e := Recovery(WithHandler(func(ctx context.Context, _, err any) error { _, ok := ctx.Value(Latency{}).(float64) if !ok { t.Errorf("not latency") } return errors.InternalServer("RECOVERY", fmt.Sprintf("panic triggered: %v", err)) }))(next)(context.Background(), "panic") t.Logf("succ and reason is %v", e) } func TestNotPanic(t *testing.T) { next := func(_ context.Context, req any) (any, error) { return req.(string) + "https://go-kratos.dev", nil } _, e := Recovery(WithHandler(func(_ context.Context, _ any, err any) error { return errors.InternalServer("RECOVERY", fmt.Sprintf("panic triggered: %v", err)) }))(next)(context.Background(), "notPanic") if e != nil { t.Errorf("e isn't nil") } } ================================================ FILE: middleware/selector/selector.go ================================================ package selector import ( "context" "regexp" "strings" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) type ( transporter func(ctx context.Context) (transport.Transporter, bool) MatchFunc func(ctx context.Context, operation string) bool ) var ( // serverTransporter is get server transport.Transporter from ctx serverTransporter transporter = func(ctx context.Context) (transport.Transporter, bool) { return transport.FromServerContext(ctx) } // clientTransporter is get client transport.Transporter from ctx clientTransporter transporter = func(ctx context.Context) (transport.Transporter, bool) { return transport.FromClientContext(ctx) } ) // Builder is a selector builder type Builder struct { client bool prefix []string regex []string path []string match MatchFunc compiled []*regexp.Regexp ms []middleware.Middleware } // Server selector middleware func Server(ms ...middleware.Middleware) *Builder { return &Builder{ms: ms} } // Client selector middleware func Client(ms ...middleware.Middleware) *Builder { return &Builder{client: true, ms: ms} } // Prefix is with Builder's prefix func (b *Builder) Prefix(prefix ...string) *Builder { b.prefix = prefix return b } // Regex is with Builder's regex func (b *Builder) Regex(regex ...string) *Builder { b.regex = regex return b } // Path is with Builder's path func (b *Builder) Path(path ...string) *Builder { b.path = path return b } // Match is with Builder's match func (b *Builder) Match(fn MatchFunc) *Builder { b.match = fn return b } // Build is Builder's Build, for example: Server().Path(m1,m2).Build() func (b *Builder) Build() middleware.Middleware { var transporter func(ctx context.Context) (transport.Transporter, bool) if b.client { transporter = clientTransporter } else { transporter = serverTransporter } b.compiled = make([]*regexp.Regexp, 0, len(b.regex)) for _, regex := range b.regex { if r, err := regexp.Compile(regex); err == nil { b.compiled = append(b.compiled, r) } } return selector(transporter, b.matches, b.ms...) } // matches is match operation compliance Builder func (b *Builder) matches(ctx context.Context, transporter transporter) bool { info, ok := transporter(ctx) if !ok { return false } operation := info.Operation() for _, prefix := range b.prefix { if prefixMatch(prefix, operation) { return true } } for _, r := range b.compiled { if r.FindString(operation) == operation { return true } } for _, path := range b.path { if pathMatch(path, operation) { return true } } if b.match != nil { if b.match(ctx, operation) { return true } } return false } // selector middleware func selector(transporter transporter, match func(context.Context, transporter) bool, ms ...middleware.Middleware) middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if !match(ctx, transporter) { return handler(ctx, req) } return middleware.Chain(ms...)(handler)(ctx, req) } } } func pathMatch(path string, operation string) bool { return path == operation } func prefixMatch(prefix string, operation string) bool { return strings.HasPrefix(operation, prefix) } ================================================ FILE: middleware/selector/selector_test.go ================================================ package selector import ( "context" "reflect" "strings" "testing" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) var _ transport.Transporter = (*Transport)(nil) type Transport struct { kind transport.Kind endpoint string operation string headers *mockHeader } func (tr *Transport) Kind() transport.Kind { return tr.kind } func (tr *Transport) Endpoint() string { return tr.endpoint } func (tr *Transport) Operation() string { return tr.operation } func (tr *Transport) RequestHeader() transport.Header { return tr.headers } func (tr *Transport) ReplyHeader() transport.Header { return nil } type mockHeader struct { m map[string][]string } func (m *mockHeader) Get(key string) string { vals := m.m[key] if len(vals) > 0 { return vals[0] } return "" } func (m *mockHeader) Set(key, value string) { m.m[key] = []string{value} } func (m *mockHeader) Add(key, value string) { m.m[key] = append(m.m[key], value) } func (m *mockHeader) Keys() []string { keys := make([]string, 0, len(m.m)) for k := range m.m { keys = append(keys, k) } return keys } func (m *mockHeader) Values(key string) []string { return m.m[key] } func TestMatch(t *testing.T) { tests := []struct { name string ctx context.Context }{ // TODO: Add test cases. { name: "/hello/world", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/hello/world"}), }, { name: "/hi/world", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/hi/world"}), }, { name: "/test/1234", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/test/1234"}), }, { name: "/example/kratos", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/example/kratos"}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { next := func(_ context.Context, req any) (any, error) { t.Log(req) return "reply", nil } next = Server(testMiddleware).Prefix("/hello/").Regex(`/test/[0-9]+`). Path("/example/kratos").Build()(next) _, _ = next(test.ctx, test.name) }) } } func TestMatchClient(t *testing.T) { tests := []struct { name string ctx context.Context }{ // TODO: Add test cases. { name: "/hello/world", ctx: transport.NewClientContext(context.Background(), &Transport{operation: "/hello/world"}), }, { name: "/hi/world", ctx: transport.NewClientContext(context.Background(), &Transport{operation: "/hi/world"}), }, { name: "/test/1234", ctx: transport.NewClientContext(context.Background(), &Transport{operation: "/test/1234"}), }, { name: "/example/kratos", ctx: transport.NewClientContext(context.Background(), &Transport{operation: "/example/kratos"}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { next := func(_ context.Context, req any) (any, error) { t.Log(req) return "reply", nil } next = Client(testMiddleware).Prefix("/hello/").Regex(`/test/[0-9]+`). Path("/example/kratos").Build()(next) _, _ = next(test.ctx, test.name) }) } } func TestFunc(t *testing.T) { tests := []struct { name string ctx context.Context }{ { name: "/hello.Update/world", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/hello.Update/world"}), }, { name: "/hi.Create/world", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/hi.Create/world"}), }, { name: "/test.Name/1234", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/test.Name/1234"}), }, { name: "/go-kratos.dev/kratos", ctx: transport.NewServerContext(context.Background(), &Transport{operation: "/go-kratos.dev/kratos"}), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { next := func(_ context.Context, req any) (any, error) { t.Log(req) return "reply", nil } next = Server(testMiddleware).Match(func(_ context.Context, operation string) bool { if strings.HasPrefix(operation, "/go-kratos.dev") || strings.HasSuffix(operation, "world") { return true } return false }).Build()(next) reply, err := next(test.ctx, test.name) if err != nil { t.Errorf("expect error is nil, but got %v", err) } if !reflect.DeepEqual(reply, "reply") { t.Errorf("expect reply is reply,but got %v", reply) } }) } } func TestHeaderFunc(t *testing.T) { tests := []struct { name string ctx context.Context }{ { name: "/hello.Update/world", ctx: transport.NewServerContext(context.Background(), &Transport{ operation: "/hello.Update/world", headers: &mockHeader{map[string][]string{"X-Test": {"test"}}}, }), }, { name: "/hi.Create/world", ctx: transport.NewServerContext(context.Background(), &Transport{ operation: "/hi.Create/world", headers: &mockHeader{map[string][]string{"X-Test": {"test2"}, "go-kratos": {"kratos"}}}, }), }, { name: "/test.Name/1234", ctx: transport.NewServerContext(context.Background(), &Transport{ operation: "/test.Name/1234", headers: &mockHeader{map[string][]string{"X-Test": {"test3"}}}, }), }, { name: "/go-kratos.dev/kratos", ctx: transport.NewServerContext(context.Background(), &Transport{ operation: "/go-kratos.dev/kratos", headers: &mockHeader{map[string][]string{"X-Test": {"test"}}}, }), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { next := func(_ context.Context, req any) (any, error) { t.Log(req) return "reply", nil } next = Server(testMiddleware).Match(func(ctx context.Context, _ string) bool { tr, ok := transport.FromServerContext(ctx) if !ok { return false } if tr.RequestHeader().Get("X-Test") == "test" { return true } if tr.RequestHeader().Get("go-kratos") == "kratos" { return true } return false }).Build()(next) reply, err := next(test.ctx, test.name) if err != nil { t.Errorf("expect error is nil, but got %v", err) } if !reflect.DeepEqual(reply, "reply") { t.Errorf("expect reply is reply,but got %v", reply) } }) } } func testMiddleware(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { reply, err = handler(ctx, req) return } } func Test_RegexMatch(t *testing.T) { tests := []struct { name string regex []string operation string want bool }{ { name: "exact match with digits", regex: []string{`/test/[0-9]+`}, operation: "/test/1234", want: true, }, { name: "no match", regex: []string{`/test/[0-9]+`}, operation: "/test/abc", want: false, }, { name: "multiple patterns first matches", regex: []string{`/api/v[0-9]+/.*`, `/test/.*`}, operation: "/api/v2/users", want: true, }, { name: "multiple patterns second matches", regex: []string{`/api/v[0-9]+/.*`, `/test/.*`}, operation: "/test/hello", want: true, }, { name: "multiple patterns none match", regex: []string{`/api/v[0-9]+/.*`, `/test/[0-9]+`}, operation: "/other/path", want: false, }, { name: "invalid regex is skipped", regex: []string{"^\b(?"}, operation: "something", want: false, }, { name: "invalid regex mixed with valid", regex: []string{"^\b(?", `/test/[0-9]+`}, operation: "/test/1234", want: true, }, { name: "empty regex list", regex: []string{}, operation: "/test/1234", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var middlewareApplied bool markMiddleware := func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (any, error) { middlewareApplied = true return handler(ctx, req) } } next := func(_ context.Context, _ any) (any, error) { return "reply", nil } ctx := transport.NewServerContext(context.Background(), &Transport{operation: tt.operation}) handler := Server(markMiddleware).Regex(tt.regex...).Build()(next) _, _ = handler(ctx, tt.operation) if middlewareApplied != tt.want { t.Errorf("middleware applied = %v, want %v", middlewareApplied, tt.want) } }) } } func Test_InvalidRegexSkipped(t *testing.T) { b := Server(testMiddleware).Regex("^\b(?", `/valid/[0-9]+`) m := b.Build() if m == nil { t.Fatal("Build() must not return nil") } if len(b.compiled) != 1 { t.Errorf("expected 1 compiled regex, got %d", len(b.compiled)) } } func Test_matches(t *testing.T) { b := Builder{} if b.matches(context.Background(), func(_ context.Context) (transport.Transporter, bool) { return nil, false }) { t.Error("The matches method must return false.") } } ================================================ FILE: middleware/tracing/metadata.go ================================================ package tracing import ( "context" "go.opentelemetry.io/otel/propagation" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/metadata" ) const serviceHeader = "x-md-service-name" // Metadata is tracing metadata propagator type Metadata struct{} var _ propagation.TextMapPropagator = Metadata{} // Inject sets metadata key-values from ctx into the carrier. func (b Metadata) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { app, ok := kratos.FromContext(ctx) if ok { carrier.Set(serviceHeader, app.Name()) } } // Extract returns a copy of parent with the metadata from the carrier added. func (b Metadata) Extract(parent context.Context, carrier propagation.TextMapCarrier) context.Context { name := carrier.Get(serviceHeader) if name == "" { return parent } if md, ok := metadata.FromServerContext(parent); ok { md.Set(serviceHeader, name) return parent } md := metadata.New() md.Set(serviceHeader, name) parent = metadata.NewServerContext(parent, md) return parent } // Fields returns the keys whose values are set with Inject. func (b Metadata) Fields() []string { return []string{serviceHeader} } ================================================ FILE: middleware/tracing/metadata_test.go ================================================ package tracing import ( "context" "reflect" "testing" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/metadata" "go.opentelemetry.io/otel/propagation" ) func TestMetadata_Inject(t *testing.T) { type args struct { appName string carrier propagation.TextMapCarrier } tests := []struct { name string args args want string }{ { name: "https://go-kratos.dev", args: args{"https://go-kratos.dev", propagation.HeaderCarrier{}}, want: "https://go-kratos.dev", }, { name: "https://github.com/go-kratos/kratos", args: args{"https://github.com/go-kratos/kratos", propagation.HeaderCarrier{"mode": []string{"test"}}}, want: "https://github.com/go-kratos/kratos", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := kratos.New(kratos.Name(tt.args.appName)) ctx := kratos.NewContext(context.Background(), a) m := new(Metadata) m.Inject(ctx, tt.args.carrier) if res := tt.args.carrier.Get(serviceHeader); tt.want != res { t.Errorf("Get(serviceHeader) :%s want: %s", res, tt.want) } }) } } func TestMetadata_Extract(t *testing.T) { type args struct { parent context.Context carrier propagation.TextMapCarrier } tests := []struct { name string args args want string crash bool }{ { name: "https://go-kratos.dev", args: args{ parent: context.Background(), carrier: propagation.HeaderCarrier{"X-Md-Service-Name": []string{"https://go-kratos.dev"}}, }, want: "https://go-kratos.dev", }, { name: "https://github.com/go-kratos/kratos", args: args{ parent: metadata.NewServerContext(context.Background(), metadata.Metadata{}), carrier: propagation.HeaderCarrier{"X-Md-Service-Name": []string{"https://github.com/go-kratos/kratos"}}, }, want: "https://github.com/go-kratos/kratos", }, { name: "https://github.com/go-kratos/kratos", args: args{ parent: metadata.NewServerContext(context.Background(), metadata.Metadata{}), carrier: propagation.HeaderCarrier{"X-Md-Service-Name": nil}, }, crash: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := Metadata{} ctx := b.Extract(tt.args.parent, tt.args.carrier) md, ok := metadata.FromServerContext(ctx) if !ok { if tt.crash { return } t.Errorf("expect %v, got %v", true, ok) } if !reflect.DeepEqual(md.Get(serviceHeader), tt.want) { t.Errorf("expect %v, got %v", tt.want, md.Get(serviceHeader)) } }) } } func TestFields(t *testing.T) { b := Metadata{} if !reflect.DeepEqual(b.Fields(), []string{"x-md-service-name"}) { t.Errorf("expect %v, got %v", []string{"x-md-service-name"}, b.Fields()) } } ================================================ FILE: middleware/tracing/span.go ================================================ package tracing import ( "context" "net" "net/url" "strings" "github.com/go-kratos/kratos/v2/metadata" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/peer" "google.golang.org/protobuf/proto" ) func setClientSpan(ctx context.Context, span trace.Span, m any) { var ( attrs []attribute.KeyValue remote string operation string rpcKind string ) tr, ok := transport.FromClientContext(ctx) if ok { operation = tr.Operation() rpcKind = tr.Kind().String() switch tr.Kind() { case transport.KindHTTP: if ht, ok := tr.(http.Transporter); ok { method := ht.Request().Method route := ht.PathTemplate() path := ht.Request().URL.Path attrs = append(attrs, semconv.HTTPMethodKey.String(method)) attrs = append(attrs, semconv.HTTPRouteKey.String(route)) attrs = append(attrs, semconv.HTTPTargetKey.String(path)) remote = ht.Request().Host } case transport.KindGRPC: remote, _ = parseTarget(tr.Endpoint()) } } attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind)) _, mAttrs := parseFullMethod(operation) attrs = append(attrs, mAttrs...) if remote != "" { attrs = append(attrs, peerAttr(remote)...) } if p, ok := m.(proto.Message); ok { attrs = append(attrs, attribute.Key("send_msg.size").Int(proto.Size(p))) } span.SetAttributes(attrs...) } func setServerSpan(ctx context.Context, span trace.Span, m any) { var ( attrs []attribute.KeyValue remote string operation string rpcKind string ) tr, ok := transport.FromServerContext(ctx) if ok { operation = tr.Operation() rpcKind = tr.Kind().String() switch tr.Kind() { case transport.KindHTTP: if ht, ok := tr.(http.Transporter); ok { method := ht.Request().Method route := ht.PathTemplate() path := ht.Request().URL.Path attrs = append(attrs, semconv.HTTPMethodKey.String(method)) attrs = append(attrs, semconv.HTTPRouteKey.String(route)) attrs = append(attrs, semconv.HTTPTargetKey.String(path)) remote = ht.Request().RemoteAddr } case transport.KindGRPC: if p, ok := peer.FromContext(ctx); ok { remote = p.Addr.String() } } } attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind)) _, mAttrs := parseFullMethod(operation) attrs = append(attrs, mAttrs...) attrs = append(attrs, peerAttr(remote)...) if p, ok := m.(proto.Message); ok { attrs = append(attrs, attribute.Key("recv_msg.size").Int(proto.Size(p))) } if md, ok := metadata.FromServerContext(ctx); ok { attrs = append(attrs, semconv.PeerServiceKey.String(md.Get(serviceHeader))) } span.SetAttributes(attrs...) } // parseFullMethod returns a span name following the OpenTelemetry semantic // conventions as well as all applicable span attribute.KeyValue attributes based // on a gRPC's FullMethod. func parseFullMethod(fullMethod string) (string, []attribute.KeyValue) { name := strings.TrimLeft(fullMethod, "/") parts := strings.SplitN(name, "/", 2) if len(parts) != 2 { //nolint:mnd // Invalid format, does not follow `/package.service/method`. return name, []attribute.KeyValue{attribute.Key("rpc.operation").String(fullMethod)} } var attrs []attribute.KeyValue if service := parts[0]; service != "" { attrs = append(attrs, semconv.RPCServiceKey.String(service)) } if method := parts[1]; method != "" { attrs = append(attrs, semconv.RPCMethodKey.String(method)) } return name, attrs } // peerAttr returns attributes about the peer address. func peerAttr(addr string) []attribute.KeyValue { host, port, err := net.SplitHostPort(addr) if err != nil { return []attribute.KeyValue(nil) } if host == "" { host = "127.0.0.1" } return []attribute.KeyValue{ semconv.NetPeerIPKey.String(host), semconv.NetPeerPortKey.String(port), } } func parseTarget(endpoint string) (address string, err error) { var u *url.URL u, err = url.Parse(endpoint) if err != nil { if u, err = url.Parse("http://" + endpoint); err != nil { return "", err } return u.Host, nil } if len(u.Path) > 1 { return u.Path[1:], nil } return endpoint, nil } ================================================ FILE: middleware/tracing/span_test.go ================================================ package tracing import ( "context" "net" "net/http" "reflect" "testing" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "google.golang.org/grpc/peer" "go.opentelemetry.io/otel/trace/noop" "github.com/go-kratos/kratos/v2/internal/testdata/binding" "github.com/go-kratos/kratos/v2/metadata" "github.com/go-kratos/kratos/v2/transport" ) func Test_parseFullMethod(t *testing.T) { tests := []struct { name string fullMethod string want string wantAttr []attribute.KeyValue }{ { name: "/foo.bar/hello", fullMethod: "/foo.bar/hello", want: "foo.bar/hello", wantAttr: []attribute.KeyValue{ semconv.RPCServiceKey.String("foo.bar"), semconv.RPCMethodKey.String("hello"), }, }, { name: "/foo.bar/hello/world", fullMethod: "/foo.bar/hello/world", want: "foo.bar/hello/world", wantAttr: []attribute.KeyValue{ semconv.RPCServiceKey.String("foo.bar"), semconv.RPCMethodKey.String("hello/world"), }, }, { name: "/hello", fullMethod: "/hello", want: "hello", wantAttr: []attribute.KeyValue{attribute.Key("rpc.operation").String("/hello")}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := parseFullMethod(tt.fullMethod) if got != tt.want { t.Errorf("parseFullMethod() got = %v, want %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.wantAttr) { t.Errorf("parseFullMethod() got1 = %v, want %v", got1, tt.wantAttr) } }) } } func Test_peerAttr(t *testing.T) { tests := []struct { name string addr string want []attribute.KeyValue }{ { name: "nil addr", addr: ":8080", want: []attribute.KeyValue{ semconv.NetPeerIPKey.String("127.0.0.1"), semconv.NetPeerPortKey.String("8080"), }, }, { name: "normal addr without port", addr: "192.168.0.1", want: []attribute.KeyValue(nil), }, { name: "normal addr with port", addr: "192.168.0.1:8080", want: []attribute.KeyValue{ semconv.NetPeerIPKey.String("192.168.0.1"), semconv.NetPeerPortKey.String("8080"), }, }, { name: "dns addr", addr: "foo:8080", want: []attribute.KeyValue{ semconv.NetPeerIPKey.String("foo"), semconv.NetPeerPortKey.String("8080"), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := peerAttr(tt.addr); !reflect.DeepEqual(got, tt.want) { t.Errorf("peerAttr() = %v, want %v", got, tt.want) } }) } } func Test_parseTarget(t *testing.T) { tests := []struct { name string endpoint string wantAddress string wantErr bool }{ { name: "http", endpoint: "http://foo.bar:8080", wantAddress: "http://foo.bar:8080", wantErr: false, }, { name: "http", endpoint: "http://127.0.0.1:8080", wantAddress: "http://127.0.0.1:8080", wantErr: false, }, { name: "without protocol", endpoint: "foo.bar:8080", wantAddress: "foo.bar:8080", wantErr: false, }, { name: "grpc", endpoint: "grpc://foo.bar", wantAddress: "grpc://foo.bar", wantErr: false, }, { name: "with path", endpoint: "/foo", wantAddress: "foo", wantErr: false, }, { name: "with path", endpoint: "http://127.0.0.1/hello", wantAddress: "hello", wantErr: false, }, { name: "empty", endpoint: "%%", wantAddress: "", wantErr: true, }, { name: "invalid path", endpoint: "//%2F/#%2Fanother", wantAddress: "", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotAddress, err := parseTarget(tt.endpoint) if (err != nil) != tt.wantErr { t.Errorf("parseTarget() error = %v, wantErr %v", err, tt.wantErr) return } if gotAddress != tt.wantAddress { t.Errorf("parseTarget() = %v, want %v", gotAddress, tt.wantAddress) } }) } } func TestSetServerSpan(_ *testing.T) { ctx := context.Background() _, span := noop.NewTracerProvider().Tracer("Tracer").Start(ctx, "Spanname") // Handle without Transport context setServerSpan(ctx, span, nil) // Handle with proto message m := &binding.HelloRequest{} setServerSpan(ctx, span, m) // Handle with metadata context ctx = metadata.NewServerContext(ctx, metadata.New()) setServerSpan(ctx, span, m) // Handle with KindHTTP transport context mt := &mockTransport{ kind: transport.KindHTTP, } mt.request, _ = http.NewRequest(http.MethodGet, "/endpoint", nil) ctx = transport.NewServerContext(ctx, mt) setServerSpan(ctx, span, m) // Handle with KindGRPC transport context mt.kind = transport.KindGRPC ctx = transport.NewServerContext(ctx, mt) ip, _ := net.ResolveIPAddr("ip", "1.1.1.1") ctx = peer.NewContext(ctx, &peer.Peer{ Addr: ip, }) setServerSpan(ctx, span, m) } func TestSetClientSpan(_ *testing.T) { ctx := context.Background() _, span := noop.NewTracerProvider().Tracer("Tracer").Start(ctx, "Spanname") // Handle without Transport context setClientSpan(ctx, span, nil) // Handle with proto message m := &binding.HelloRequest{} setClientSpan(ctx, span, m) // Handle with metadata context ctx = metadata.NewClientContext(ctx, metadata.New()) setClientSpan(ctx, span, m) // Handle with KindHTTP transport context mt := &mockTransport{ kind: transport.KindHTTP, } mt.request, _ = http.NewRequest(http.MethodGet, "/endpoint", nil) mt.request.Host = "MyServer" ctx = transport.NewClientContext(ctx, mt) setClientSpan(ctx, span, m) // Handle with KindGRPC transport context mt.kind = transport.KindGRPC ctx = transport.NewClientContext(ctx, mt) ip, _ := net.ResolveIPAddr("ip", "1.1.1.1") ctx = peer.NewContext(ctx, &peer.Peer{ Addr: ip, }) setClientSpan(ctx, span, m) // Handle without Host request ctx = transport.NewClientContext(ctx, mt) setClientSpan(ctx, span, m) } ================================================ FILE: middleware/tracing/statshandler.go ================================================ package tracing import ( "context" "fmt" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" ) // ClientHandler is tracing ClientHandler type ClientHandler struct{} // HandleConn exists to satisfy gRPC stats.Handler. func (c *ClientHandler) HandleConn(_ context.Context, _ stats.ConnStats) { fmt.Println("Handle connection.") } // TagConn exists to satisfy gRPC stats.Handler. func (c *ClientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { return ctx } // HandleRPC implements per-RPC tracing and stats instrumentation. func (c *ClientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { if _, ok := rs.(*stats.OutHeader); !ok { return } p, ok := peer.FromContext(ctx) if !ok { return } span := trace.SpanFromContext(ctx) if span.SpanContext().IsValid() { span.SetAttributes(peerAttr(p.Addr.String())...) } } // TagRPC implements per-RPC context management. func (c *ClientHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { return ctx } ================================================ FILE: middleware/tracing/statshandler_test.go ================================================ package tracing import ( "context" "net" "testing" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" ) type ctxKey string const testKey ctxKey = "MY_TEST_KEY" func TestClient_HandleConn(_ *testing.T) { (&ClientHandler{}).HandleConn(context.Background(), nil) } func TestClient_TagConn(t *testing.T) { client := &ClientHandler{} ctx := context.WithValue(context.Background(), testKey, 123) if client.TagConn(ctx, nil).Value(testKey) != 123 { t.Errorf(`The context value must be 123 for the "MY_KEY_TEST" key, %v given.`, client.TagConn(ctx, nil).Value(testKey)) } } func TestClient_TagRPC(t *testing.T) { client := &ClientHandler{} ctx := context.WithValue(context.Background(), testKey, 123) if client.TagRPC(ctx, nil).Value(testKey) != 123 { t.Errorf(`The context value must be 123 for the "MY_KEY_TEST" key, %v given.`, client.TagConn(ctx, nil).Value(testKey)) } } type mockSpan struct { trace.Span mockSpanCtx *trace.SpanContext } func (m *mockSpan) SpanContext() trace.SpanContext { return *m.mockSpanCtx } func TestClient_HandleRPC(_ *testing.T) { client := &ClientHandler{} ctx := context.Background() rs := stats.OutHeader{} // Handle stats.RPCStats is not type of stats.OutHeader case client.HandleRPC(context.TODO(), nil) // Handle context doesn't have the peerkey filled with a Peer instance client.HandleRPC(ctx, &rs) // Handle context with the peerkey filled with a Peer instance ip, _ := net.ResolveIPAddr("ip", "1.1.1.1") ctx = peer.NewContext(ctx, &peer.Peer{ Addr: ip, }) client.HandleRPC(ctx, &rs) // Handle context with Span _, span := noop.NewTracerProvider().Tracer("Tracer").Start(ctx, "Spanname") spanCtx := trace.SpanContext{} spanID := [8]byte{12, 12, 12, 12, 12, 12, 12, 12} traceID := [16]byte{12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12} spanCtx = spanCtx.WithTraceID(traceID) spanCtx = spanCtx.WithSpanID(spanID) mSpan := mockSpan{ Span: span, mockSpanCtx: &spanCtx, } ctx = trace.ContextWithSpan(ctx, &mSpan) client.HandleRPC(ctx, &rs) } ================================================ FILE: middleware/tracing/tracer.go ================================================ package tracing import ( "context" "fmt" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/errors" ) // Tracer is otel span tracer type Tracer struct { tracer trace.Tracer kind trace.SpanKind opt *options } // NewTracer create tracer instance func NewTracer(kind trace.SpanKind, opts ...Option) *Tracer { op := options{ propagator: propagation.NewCompositeTextMapPropagator(Metadata{}, propagation.Baggage{}, propagation.TraceContext{}), tracerName: "kratos", } for _, o := range opts { o(&op) } if op.tracerProvider == nil { op.tracerProvider = otel.GetTracerProvider() } switch kind { case trace.SpanKindClient: return &Tracer{tracer: op.tracerProvider.Tracer(op.tracerName), kind: kind, opt: &op} case trace.SpanKindServer: return &Tracer{tracer: op.tracerProvider.Tracer(op.tracerName), kind: kind, opt: &op} default: panic(fmt.Sprintf("unsupported span kind: %v", kind)) } } // Start start tracing span func (t *Tracer) Start(ctx context.Context, operation string, carrier propagation.TextMapCarrier) (context.Context, trace.Span) { if t.kind == trace.SpanKindServer { ctx = t.opt.propagator.Extract(ctx, carrier) } ctx, span := t.tracer.Start(ctx, operation, trace.WithSpanKind(t.kind), ) if t.kind == trace.SpanKindClient { t.opt.propagator.Inject(ctx, carrier) } return ctx, span } // End finish tracing span func (t *Tracer) End(_ context.Context, span trace.Span, m any, err error) { if err != nil { span.RecordError(err) if e := errors.FromError(err); e != nil { span.SetAttributes(attribute.Key("rpc.status_code").Int64(int64(e.Code))) } span.SetStatus(codes.Error, err.Error()) } else { span.SetStatus(codes.Ok, "OK") } if p, ok := m.(proto.Message); ok { if t.kind == trace.SpanKindServer { span.SetAttributes(attribute.Key("send_msg.size").Int(proto.Size(p))) } else { span.SetAttributes(attribute.Key("recv_msg.size").Int(proto.Size(p))) } } span.End() } ================================================ FILE: middleware/tracing/tracer_test.go ================================================ package tracing import ( "context" "errors" "testing" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" "github.com/go-kratos/kratos/v2/internal/testdata/binding" ) func TestNewTracer(t *testing.T) { tracer := NewTracer(trace.SpanKindClient, func(o *options) { o.tracerProvider = noop.NewTracerProvider() }) if tracer.kind != trace.SpanKindClient { t.Errorf("The tracer kind must be equal to trace.SpanKindClient, %v given.", tracer.kind) } defer func() { if recover() == nil { t.Error("The NewTracer with an invalid SpanKindMustCrash must panic") } }() _ = NewTracer(666, func(o *options) { o.tracerProvider = noop.NewTracerProvider() }) } func TestTracer_End(_ *testing.T) { tracer := NewTracer(trace.SpanKindClient, func(o *options) { o.tracerProvider = noop.NewTracerProvider() }) ctx, span := noop.NewTracerProvider().Tracer("noop").Start(context.Background(), "noopSpan") // Handle with error case tracer.End(ctx, span, nil, errors.New("dummy error")) // Handle without error case tracer.End(ctx, span, nil, nil) m := &binding.HelloRequest{} // Handle the trace KindServer tracer = NewTracer(trace.SpanKindServer, func(o *options) { o.tracerProvider = noop.NewTracerProvider() }) tracer.End(ctx, span, m, nil) tracer = NewTracer(trace.SpanKindClient, func(o *options) { o.tracerProvider = noop.NewTracerProvider() }) tracer.End(ctx, span, m, nil) } ================================================ FILE: middleware/tracing/tracing.go ================================================ package tracing import ( "context" "github.com/go-kratos/kratos/v2/log" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) // Option is tracing option. type Option func(*options) type options struct { tracerName string tracerProvider trace.TracerProvider propagator propagation.TextMapPropagator } // WithPropagator with tracer propagator. func WithPropagator(propagator propagation.TextMapPropagator) Option { return func(opts *options) { opts.propagator = propagator } } // WithTracerProvider with tracer provider. // By default, it uses the global provider that is set by otel.SetTracerProvider(provider). func WithTracerProvider(provider trace.TracerProvider) Option { return func(opts *options) { opts.tracerProvider = provider } } // WithTracerName with tracer name func WithTracerName(tracerName string) Option { return func(opts *options) { opts.tracerName = tracerName } } // Server returns a new server middleware for OpenTelemetry. func Server(opts ...Option) middleware.Middleware { tracer := NewTracer(trace.SpanKindServer, opts...) return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if tr, ok := transport.FromServerContext(ctx); ok { var span trace.Span ctx, span = tracer.Start(ctx, tr.Operation(), tr.RequestHeader()) setServerSpan(ctx, span, req) defer func() { tracer.End(ctx, span, reply, err) }() } return handler(ctx, req) } } } // Client returns a new client middleware for OpenTelemetry. func Client(opts ...Option) middleware.Middleware { tracer := NewTracer(trace.SpanKindClient, opts...) return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if tr, ok := transport.FromClientContext(ctx); ok { var span trace.Span ctx, span = tracer.Start(ctx, tr.Operation(), tr.RequestHeader()) setClientSpan(ctx, span, req) defer func() { tracer.End(ctx, span, reply, err) }() } return handler(ctx, req) } } } // TraceID returns a traceid valuer. func TraceID() log.Valuer { return func(ctx context.Context) any { if span := trace.SpanContextFromContext(ctx); span.HasTraceID() { return span.TraceID().String() } return "" } } // SpanID returns a spanid valuer. func SpanID() log.Valuer { return func(ctx context.Context) any { if span := trace.SpanContextFromContext(ctx); span.HasSpanID() { return span.SpanID().String() } return "" } } ================================================ FILE: middleware/tracing/tracing_test.go ================================================ package tracing import ( "context" "net/http" "net/http/httptest" "os" "reflect" "testing" "go.opentelemetry.io/otel/propagation" tracesdk "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport" ) var _ transport.Transporter = (*mockTransport)(nil) type headerCarrier http.Header // Get returns the value associated with the passed key. func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key) } // Set stores the key-value pair. func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) } // Add value to the key-value pair. func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) } // Keys lists the keys stored in this carrier. func (hc headerCarrier) Keys() []string { keys := make([]string, 0, len(hc)) for k := range http.Header(hc) { keys = append(keys, k) } return keys } // Values returns a slice value associated with the passed key. func (hc headerCarrier) Values(key string) []string { return http.Header(hc).Values(key) } type mockTransport struct { kind transport.Kind endpoint string operation string header headerCarrier request *http.Request } func (tr *mockTransport) Kind() transport.Kind { return tr.kind } func (tr *mockTransport) Endpoint() string { return tr.endpoint } func (tr *mockTransport) Operation() string { return tr.operation } func (tr *mockTransport) RequestHeader() transport.Header { return tr.header } func (tr *mockTransport) ReplyHeader() transport.Header { return tr.header } func (tr *mockTransport) Request() *http.Request { if tr.request == nil { rq, _ := http.NewRequest(http.MethodGet, "/endpoint", nil) return rq } return tr.request } func (tr *mockTransport) PathTemplate() string { return "" } func (tr *mockTransport) Response() http.ResponseWriter { return httptest.NewRecorder() } func TestTracer(t *testing.T) { carrier := headerCarrier{} tp := tracesdk.NewTracerProvider(tracesdk.WithSampler(tracesdk.TraceIDRatioBased(0))) // caller use Inject cliTracer := NewTracer( trace.SpanKindClient, WithTracerProvider(tp), WithPropagator( propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}), ), ) ts := &mockTransport{kind: transport.KindHTTP, header: carrier} ctx, aboveSpan := cliTracer.Start(transport.NewClientContext(context.Background(), ts), ts.Operation(), ts.RequestHeader()) defer cliTracer.End(ctx, aboveSpan, nil, nil) // server use Extract fetch traceInfo from carrier svrTracer := NewTracer(trace.SpanKindServer, WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}))) ts = &mockTransport{kind: transport.KindHTTP, header: carrier} ctx, span := svrTracer.Start(transport.NewServerContext(ctx, ts), ts.Operation(), ts.RequestHeader()) defer svrTracer.End(ctx, span, nil, nil) if aboveSpan.SpanContext().TraceID() != span.SpanContext().TraceID() { t.Fatalf("TraceID failed to deliver") } if v, ok := transport.FromClientContext(ctx); !ok || len(v.RequestHeader().Keys()) == 0 { t.Fatalf("traceHeader failed to deliver") } } func TestServer(t *testing.T) { tr := &mockTransport{ kind: transport.KindHTTP, endpoint: "server:2233", operation: "/test.server/hello", header: headerCarrier{}, } tracer := NewTracer( trace.SpanKindClient, WithTracerProvider(tracesdk.NewTracerProvider()), ) logger := log.NewStdLogger(os.Stdout) logger = log.With(logger, "span_id", SpanID()) logger = log.With(logger, "trace_id", TraceID()) var ( childSpanID string childTraceID string ) next := func(ctx context.Context, req any) (any, error) { _ = log.WithContext(ctx, logger).Log(log.LevelInfo, "kind", "server", ) childSpanID = SpanID()(ctx).(string) childTraceID = TraceID()(ctx).(string) return req.(string) + "https://go-kratos.dev", nil } var ctx context.Context ctx, span := tracer.Start( transport.NewServerContext(context.Background(), tr), tr.Operation(), tr.RequestHeader(), ) _, err := Server( WithTracerProvider(tracesdk.NewTracerProvider()), WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})), )(next)(ctx, "test server: ") span.End() if err != nil { t.Errorf("expected nil, got %v", err) } if childSpanID == "" { t.Errorf("expected empty, got %v", childSpanID) } if reflect.DeepEqual(span.SpanContext().SpanID().String(), childSpanID) { t.Errorf("span.SpanContext().SpanID().String()(%v) is not equal to childSpanID(%v)", span.SpanContext().SpanID().String(), childSpanID) } if !reflect.DeepEqual(span.SpanContext().TraceID().String(), childTraceID) { t.Errorf("expected %v, got %v", childTraceID, span.SpanContext().TraceID().String()) } _, err = Server( WithTracerProvider(tracesdk.NewTracerProvider()), WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})), )(next)(context.Background(), "test server: ") if err != nil { t.Errorf("expected error, got nil") } if childSpanID != "" { t.Errorf("expected empty, got %v", childSpanID) } if childTraceID != "" { t.Errorf("expected empty, got %v", childTraceID) } } func TestClient(t *testing.T) { tr := &mockTransport{ kind: transport.KindHTTP, endpoint: "server:2233", operation: "/test.server/hello", header: headerCarrier{}, } tracer := NewTracer( trace.SpanKindClient, WithTracerProvider(tracesdk.NewTracerProvider()), ) logger := log.NewStdLogger(os.Stdout) logger = log.With(logger, "span_id", SpanID()) logger = log.With(logger, "trace_id", TraceID()) var ( childSpanID string childTraceID string ) next := func(ctx context.Context, req any) (any, error) { _ = log.WithContext(ctx, logger).Log(log.LevelInfo, "kind", "client", ) childSpanID = SpanID()(ctx).(string) childTraceID = TraceID()(ctx).(string) return req.(string) + "https://go-kratos.dev", nil } var ctx context.Context ctx, span := tracer.Start( transport.NewClientContext(context.Background(), tr), tr.Operation(), tr.RequestHeader(), ) _, err := Client( WithTracerProvider(tracesdk.NewTracerProvider()), WithPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})), )(next)(ctx, "test client: ") span.End() if err != nil { t.Errorf("expected nil, got %v", err) } if childSpanID == "" { t.Errorf("expected empty, got %v", childSpanID) } if reflect.DeepEqual(span.SpanContext().SpanID().String(), childSpanID) { t.Errorf("span.SpanContext().SpanID().String()(%v) is not equal to childSpanID(%v)", span.SpanContext().SpanID().String(), childSpanID) } if !reflect.DeepEqual(span.SpanContext().TraceID().String(), childTraceID) { t.Errorf("expected %v, got %v", childTraceID, span.SpanContext().TraceID().String()) } } ================================================ FILE: middleware/validate/validate.go ================================================ package validate import ( "context" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" ) // ValidatorFunc defines a validation function type. type ValidatorFunc func(v any) error // validator is an interface for types that can validate themselves. type validator interface { Validate() error } // Validator returns a middleware that performs validation on requests. // Example usage: // // buf validate(https://github.com/bufbuild/protovalidate): // import "buf.build/go/protovalidate" // // Validator(func(v any) error { // if msg, ok := req.(proto.Message); ok { // if err := protovalidate.Validate(msg); err != nil { // return nil, err // } // } // return nil // }) // // Google AIP field behavior validate(https://google.aip.dev/203): // import "go.einride.tech/aip/fieldbehavior" // // Validator(func(v any) error { // if msg, ok := req.(proto.Message); ok { // if err := fieldbehavior.ValidateRequiredFields(msg); err != nil { // return nil, err // } // } // return nil // }) func Validator(validators ...ValidatorFunc) middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if v, ok := req.(validator); ok { if err := v.Validate(); err != nil { return nil, errors.BadRequest("VALIDATOR", err.Error()).WithCause(err) } } for _, v := range validators { if err := v(req); err != nil { return nil, errors.BadRequest("VALIDATOR", err.Error()).WithCause(err) } } return handler(ctx, req) } } } ================================================ FILE: middleware/validate/validate_test.go ================================================ package validate import ( "context" "errors" "testing" kratoserrors "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" ) // protoVali implement validate.validator type protoVali struct { name string age int isErr bool } func (v protoVali) Validate() error { if v.name == "" || v.age < 0 { return errors.New("err") } return nil } func TestTable(t *testing.T) { var mock middleware.Handler = func(context.Context, any) (any, error) { return nil, nil } tests := []protoVali{ {"v1", 365, false}, {"v2", -1, true}, {"", 365, true}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { v := Validator()(mock) _, err := v(context.Background(), test) if want, have := test.isErr, kratoserrors.IsBadRequest(err); want != have { t.Errorf("fail data %v, want %v, have %v", test, want, have) } }) } } ================================================ FILE: options.go ================================================ package kratos import ( "context" "net/url" "os" "time" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/transport" ) // Option is an application option. type Option func(o *options) // options is an application options. type options struct { id string name string version string metadata map[string]string endpoints []*url.URL ctx context.Context sigs []os.Signal logger log.Logger registrar registry.Registrar registrarTimeout time.Duration stopTimeout time.Duration servers []transport.Server // Before and After funcs beforeStart []func(context.Context) error beforeStop []func(context.Context) error afterStart []func(context.Context) error afterStop []func(context.Context) error } // ID with service id. func ID(id string) Option { return func(o *options) { o.id = id } } // Name with service name. func Name(name string) Option { return func(o *options) { o.name = name } } // Version with service version. func Version(version string) Option { return func(o *options) { o.version = version } } // Metadata with service metadata. func Metadata(md map[string]string) Option { return func(o *options) { o.metadata = md } } // Endpoint with service endpoint. func Endpoint(endpoints ...*url.URL) Option { return func(o *options) { o.endpoints = endpoints } } // Context with service context. func Context(ctx context.Context) Option { return func(o *options) { o.ctx = ctx } } // Logger with service logger. func Logger(logger log.Logger) Option { return func(o *options) { o.logger = logger } } // Server with transport servers. func Server(srv ...transport.Server) Option { return func(o *options) { o.servers = srv } } // Signal with exit signals. func Signal(sigs ...os.Signal) Option { return func(o *options) { o.sigs = sigs } } // Registrar with service registry. func Registrar(r registry.Registrar) Option { return func(o *options) { o.registrar = r } } // RegistrarTimeout with registrar timeout. func RegistrarTimeout(t time.Duration) Option { return func(o *options) { o.registrarTimeout = t } } // StopTimeout with app stop timeout. func StopTimeout(t time.Duration) Option { return func(o *options) { o.stopTimeout = t } } // Before and Afters // BeforeStart run funcs before app starts func BeforeStart(fn func(context.Context) error) Option { return func(o *options) { o.beforeStart = append(o.beforeStart, fn) } } // BeforeStop run funcs before app stops func BeforeStop(fn func(context.Context) error) Option { return func(o *options) { o.beforeStop = append(o.beforeStop, fn) } } // AfterStart run funcs after app starts func AfterStart(fn func(context.Context) error) Option { return func(o *options) { o.afterStart = append(o.afterStart, fn) } } // AfterStop run funcs after app stops func AfterStop(fn func(context.Context) error) Option { return func(o *options) { o.afterStop = append(o.afterStop, fn) } } ================================================ FILE: options_test.go ================================================ package kratos import ( "context" "log" "net/url" "os" "reflect" "testing" "time" xlog "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/transport" ) func TestID(t *testing.T) { o := &options{} v := "123" ID(v)(o) if !reflect.DeepEqual(v, o.id) { t.Fatalf("o.id:%s is not equal to v:%s", o.id, v) } } func TestName(t *testing.T) { o := &options{} v := "abc" Name(v)(o) if !reflect.DeepEqual(v, o.name) { t.Fatalf("o.name:%s is not equal to v:%s", o.name, v) } } func TestVersion(t *testing.T) { o := &options{} v := "123" Version(v)(o) if !reflect.DeepEqual(v, o.version) { t.Fatalf("o.version:%s is not equal to v:%s", o.version, v) } } func TestMetadata(t *testing.T) { o := &options{} v := map[string]string{ "a": "1", "b": "2", } Metadata(v)(o) if !reflect.DeepEqual(v, o.metadata) { t.Fatalf("o.metadata:%s is not equal to v:%s", o.metadata, v) } } func TestEndpoint(t *testing.T) { o := &options{} v := []*url.URL{ {Host: "example.com"}, {Host: "foo.com"}, } Endpoint(v...)(o) if !reflect.DeepEqual(v, o.endpoints) { t.Fatalf("o.endpoints:%s is not equal to v:%s", o.endpoints, v) } } func TestContext(t *testing.T) { type ctxKey struct { Key string } o := &options{} v := context.WithValue(context.TODO(), ctxKey{Key: "context"}, "b") Context(v)(o) if !reflect.DeepEqual(v, o.ctx) { t.Fatalf("o.ctx:%s is not equal to v:%s", o.ctx, v) } } func TestLogger(t *testing.T) { o := &options{} v := xlog.NewStdLogger(log.Writer()) Logger(v)(o) if !reflect.DeepEqual(v, o.logger) { t.Fatalf("o.logger:%v is not equal to xlog.NewHelper(v):%v", o.logger, xlog.NewHelper(v)) } } type mockServer struct { stopFn func(context.Context) error } func (m *mockServer) Start(_ context.Context) error { return nil } func (m *mockServer) Stop(ctx context.Context) error { if m.stopFn != nil { return m.stopFn(ctx) } return nil } func TestServer(t *testing.T) { o := &options{} v := []transport.Server{ &mockServer{}, &mockServer{}, } Server(v...)(o) if !reflect.DeepEqual(v, o.servers) { t.Fatalf("o.servers:%s is not equal to xlog.NewHelper(v):%s", o.servers, v) } } type mockSignal struct{} func (m *mockSignal) String() string { return "sig" } func (m *mockSignal) Signal() {} func TestSignal(t *testing.T) { o := &options{} v := []os.Signal{ &mockSignal{}, &mockSignal{}, } Signal(v...)(o) if !reflect.DeepEqual(v, o.sigs) { t.Fatal("o.sigs is not equal to v") } } type mockRegistrar struct{} func (m *mockRegistrar) Register(_ context.Context, _ *registry.ServiceInstance) error { return nil } func (m *mockRegistrar) Deregister(_ context.Context, _ *registry.ServiceInstance) error { return nil } func TestRegistrar(t *testing.T) { o := &options{} v := &mockRegistrar{} Registrar(v)(o) if !reflect.DeepEqual(v, o.registrar) { t.Fatal("o.registrar is not equal to v") } } func TestRegistrarTimeout(t *testing.T) { o := &options{} v := time.Duration(123) RegistrarTimeout(v)(o) if !reflect.DeepEqual(v, o.registrarTimeout) { t.Fatal("o.registrarTimeout is not equal to v") } } func TestStopTimeout(t *testing.T) { o := &options{} v := time.Duration(123) StopTimeout(v)(o) if !reflect.DeepEqual(v, o.stopTimeout) { t.Fatal("o.stopTimeout is not equal to v") } } func TestBeforeStart(t *testing.T) { o := &options{} v := func(_ context.Context) error { t.Log("BeforeStart...") return nil } BeforeStart(v)(o) } func TestBeforeStop(t *testing.T) { o := &options{} v := func(_ context.Context) error { t.Log("BeforeStop...") return nil } BeforeStop(v)(o) } func TestAfterStart(t *testing.T) { o := &options{} v := func(_ context.Context) error { t.Log("AfterStart...") return nil } AfterStart(v)(o) } func TestAfterStop(t *testing.T) { o := &options{} v := func(_ context.Context) error { t.Log("AfterStop...") return nil } AfterStop(v)(o) } ================================================ FILE: registry/README.md ================================================ # Registry ## Consul ```shell go get -u github.com/go-kratos/kratos/contrib/registry/consul/v2 ``` ## Etcd ```shell go get -u github.com/go-kratos/kratos/contrib/registry/etcd/v2 ``` ## zookeeper ```shell go get -u github.com/go-kratos/kratos/contrib/registry/zookeeper/v2 ``` ## Nacos ```shell go get -u github.com/go-kratos/kratos/contrib/registry/nacos/v2 ``` ## kubernetes ```shell go get -u github.com/go-kratos/kratos/contrib/registry/kubernetes/v2 ``` ## polaris ```shell go get -u github.com/go-kratos/kratos/contrib/registry/polaris/v2 ``` ================================================ FILE: registry/registry.go ================================================ package registry import ( "context" "fmt" "sort" ) // Registrar is service registrar. type Registrar interface { // Register the registration. Register(ctx context.Context, service *ServiceInstance) error // Deregister the registration. Deregister(ctx context.Context, service *ServiceInstance) error } // Discovery is service discovery. type Discovery interface { // GetService return the service instances in memory according to the service name. GetService(ctx context.Context, serviceName string) ([]*ServiceInstance, error) // Watch creates a watcher according to the service name. Watch(ctx context.Context, serviceName string) (Watcher, error) } // Watcher is service watcher. type Watcher interface { // Next returns services in the following two cases: // 1.the first time to watch and the service instance list is not empty. // 2.any service instance changes found. // if the above two conditions are not met, it will block until context deadline exceeded or canceled Next() ([]*ServiceInstance, error) // Stop close the watcher. Stop() error } // ServiceInstance is an instance of a service in a discovery system. type ServiceInstance struct { // ID is the unique instance ID as registered. ID string `json:"id"` // Name is the service name as registered. Name string `json:"name"` // Version is the version of the compiled. Version string `json:"version"` // Metadata is the kv pair metadata associated with the service instance. Metadata map[string]string `json:"metadata"` // Endpoints are endpoint addresses of the service instance. // schema: // http://127.0.0.1:8000?isSecure=false // grpc://127.0.0.1:9000?isSecure=false Endpoints []string `json:"endpoints"` } func (i *ServiceInstance) String() string { return fmt.Sprintf("%s-%s", i.Name, i.ID) } // Equal returns whether i and o are equivalent. func (i *ServiceInstance) Equal(o any) bool { if i == nil && o == nil { return true } if i == nil || o == nil { return false } t, ok := o.(*ServiceInstance) if !ok { return false } if len(i.Endpoints) != len(t.Endpoints) { return false } sort.Strings(i.Endpoints) sort.Strings(t.Endpoints) for j := 0; j < len(i.Endpoints); j++ { if i.Endpoints[j] != t.Endpoints[j] { return false } } if len(i.Metadata) != len(t.Metadata) { return false } for k, v := range i.Metadata { if v != t.Metadata[k] { return false } } return i.ID == t.ID && i.Name == t.Name && i.Version == t.Version } ================================================ FILE: selector/balancer.go ================================================ package selector import ( "context" "time" ) // Balancer is balancer interface type Balancer interface { Pick(ctx context.Context, nodes []WeightedNode) (selected WeightedNode, done DoneFunc, err error) } // BalancerBuilder build balancer type BalancerBuilder interface { Build() Balancer } // WeightedNode calculates scheduling weight in real time type WeightedNode interface { Node // Raw returns the original node Raw() Node // Weight is the runtime calculated weight Weight() float64 // Pick the node Pick() DoneFunc // PickElapsed is time elapsed since the latest pick PickElapsed() time.Duration } // WeightedNodeBuilder is WeightedNode Builder type WeightedNodeBuilder interface { Build(Node) WeightedNode } ================================================ FILE: selector/default_node.go ================================================ package selector import ( "strconv" "github.com/go-kratos/kratos/v2/registry" ) var _ Node = (*DefaultNode)(nil) // DefaultNode is selector node type DefaultNode struct { scheme string addr string weight *int64 version string name string metadata map[string]string } // Scheme is node scheme func (n *DefaultNode) Scheme() string { return n.scheme } // Address is node address func (n *DefaultNode) Address() string { return n.addr } // ServiceName is node serviceName func (n *DefaultNode) ServiceName() string { return n.name } // InitialWeight is node initialWeight func (n *DefaultNode) InitialWeight() *int64 { return n.weight } // Version is node version func (n *DefaultNode) Version() string { return n.version } // Metadata is node metadata func (n *DefaultNode) Metadata() map[string]string { return n.metadata } // NewNode new node func NewNode(scheme, addr string, ins *registry.ServiceInstance) Node { n := &DefaultNode{ scheme: scheme, addr: addr, } if ins != nil { n.name = ins.Name n.version = ins.Version n.metadata = ins.Metadata if str, ok := ins.Metadata["weight"]; ok { if weight, err := strconv.ParseInt(str, 10, 64); err == nil { n.weight = &weight } } } return n } ================================================ FILE: selector/default_selector.go ================================================ package selector import ( "context" "sync/atomic" ) var ( _ Rebalancer = (*Default)(nil) _ Builder = (*DefaultBuilder)(nil) ) // Default is composite selector. type Default struct { NodeBuilder WeightedNodeBuilder Balancer Balancer nodes atomic.Value } // Select is select one node. func (d *Default) Select(ctx context.Context, opts ...SelectOption) (selected Node, done DoneFunc, err error) { var ( options SelectOptions candidates []WeightedNode ) nodes, ok := d.nodes.Load().([]WeightedNode) if !ok { return nil, nil, ErrNoAvailable } for _, o := range opts { o(&options) } if len(options.NodeFilters) > 0 { newNodes := make([]Node, len(nodes)) for i, wc := range nodes { newNodes[i] = wc } for _, filter := range options.NodeFilters { newNodes = filter(ctx, newNodes) } candidates = make([]WeightedNode, len(newNodes)) for i, n := range newNodes { candidates[i] = n.(WeightedNode) } } else { candidates = nodes } if len(candidates) == 0 { return nil, nil, ErrNoAvailable } wn, done, err := d.Balancer.Pick(ctx, candidates) if err != nil { return nil, nil, err } p, ok := FromPeerContext(ctx) if ok { p.Node = wn.Raw() } return wn.Raw(), done, nil } // Apply update nodes info. func (d *Default) Apply(nodes []Node) { weightedNodes := make([]WeightedNode, 0, len(nodes)) for _, n := range nodes { weightedNodes = append(weightedNodes, d.NodeBuilder.Build(n)) } // TODO: Do not delete unchanged nodes d.nodes.Store(weightedNodes) } // DefaultBuilder is de type DefaultBuilder struct { Node WeightedNodeBuilder Balancer BalancerBuilder } // Build create builder func (db *DefaultBuilder) Build() Selector { return &Default{ NodeBuilder: db.Node, Balancer: db.Balancer.Build(), } } ================================================ FILE: selector/filter/version.go ================================================ package filter import ( "context" "github.com/go-kratos/kratos/v2/selector" ) // Version is version filter. func Version(version string) selector.NodeFilter { return func(_ context.Context, nodes []selector.Node) []selector.Node { newNodes := make([]selector.Node, 0, len(nodes)) for _, n := range nodes { if n.Version() == version { newNodes = append(newNodes, n) } } return newNodes } } ================================================ FILE: selector/filter/version_test.go ================================================ package filter import ( "context" "reflect" "testing" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) func TestVersion(t *testing.T) { f := Version("v2.0.0") var nodes []selector.Node nodes = append(nodes, selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, })) nodes = append(nodes, selector.NewNode( "http", "127.0.0.2:9090", ®istry.ServiceInstance{ ID: "127.0.0.2:9090", Name: "helloworld", Version: "v2.0.0", Endpoints: []string{"http://127.0.0.2:9090"}, })) nodes = f(context.Background(), nodes) if !reflect.DeepEqual(len(nodes), 1) { t.Errorf("expect %v, got %v", 1, len(nodes)) } if !reflect.DeepEqual(nodes[0].Address(), "127.0.0.2:9090") { t.Errorf("expect %v, got %v", nodes[0].Address(), "127.0.0.2:9090") } } ================================================ FILE: selector/filter.go ================================================ package selector import "context" // NodeFilter is select filter. type NodeFilter func(context.Context, []Node) []Node ================================================ FILE: selector/global.go ================================================ package selector var globalSelector = &wrapSelector{} var _ Builder = (*wrapSelector)(nil) // wrapSelector wrapped Selector, help override global Selector implementation. type wrapSelector struct{ Builder } // GlobalSelector returns global selector builder. func GlobalSelector() Builder { if globalSelector.Builder != nil { return globalSelector } return nil } // SetGlobalSelector set global selector builder. func SetGlobalSelector(builder Builder) { globalSelector.Builder = builder } ================================================ FILE: selector/node/direct/direct.go ================================================ package direct import ( "context" "sync/atomic" "time" "github.com/go-kratos/kratos/v2/selector" ) const ( defaultWeight = 100 ) var ( _ selector.WeightedNode = (*Node)(nil) _ selector.WeightedNodeBuilder = (*Builder)(nil) ) // Node is endpoint instance type Node struct { selector.Node // last lastPick timestamp lastPick atomic.Int64 } // Builder is direct node builder type Builder struct{} // Build create node func (*Builder) Build(n selector.Node) selector.WeightedNode { return &Node{Node: n, lastPick: atomic.Int64{}} } func (n *Node) Pick() selector.DoneFunc { now := time.Now().UnixNano() n.lastPick.Store(now) return func(context.Context, selector.DoneInfo) {} } // Weight is node effective weight func (n *Node) Weight() float64 { if n.InitialWeight() != nil { return float64(*n.InitialWeight()) } return defaultWeight } func (n *Node) PickElapsed() time.Duration { return time.Duration(time.Now().UnixNano() - n.lastPick.Load()) } func (n *Node) Raw() selector.Node { return n.Node } ================================================ FILE: selector/node/direct/direct_test.go ================================================ package direct import ( "context" "reflect" "testing" "time" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) func TestDirect(t *testing.T) { b := &Builder{} wn := b.Build(selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) done := wn.Pick() if done == nil { t.Errorf("expect %v, got %v", nil, done) } time.Sleep(time.Millisecond * 10) done(context.Background(), selector.DoneInfo{}) if !reflect.DeepEqual(float64(10), wn.Weight()) { t.Errorf("expect %v, got %v", float64(10), wn.Weight()) } if time.Millisecond*20 <= wn.PickElapsed() { t.Errorf("20ms <= wn.PickElapsed()(%s)", wn.PickElapsed()) } if time.Millisecond*10 >= wn.PickElapsed() { t.Errorf("10ms >= wn.PickElapsed()(%s)", wn.PickElapsed()) } } func TestDirectDefaultWeight(t *testing.T) { b := &Builder{} wn := b.Build(selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, })) done := wn.Pick() if done == nil { t.Errorf("expect %v, got %v", nil, done) } time.Sleep(time.Millisecond * 10) done(context.Background(), selector.DoneInfo{}) if !reflect.DeepEqual(float64(100), wn.Weight()) { t.Errorf("expect %v, got %v", float64(100), wn.Weight()) } if time.Millisecond*20 <= wn.PickElapsed() { t.Errorf("time.Millisecond*20 <= wn.PickElapsed()(%s)", wn.PickElapsed()) } if time.Millisecond*5 >= wn.PickElapsed() { t.Errorf("time.Millisecond*5 >= wn.PickElapsed()(%s)", wn.PickElapsed()) } } ================================================ FILE: selector/node/ewma/node.go ================================================ package ewma import ( "context" "math" "net" "sync/atomic" "time" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/selector" ) const ( // The mean lifetime of `cost`, it reaches its half-life after Tau*ln(2). tau = int64(time.Millisecond * 600) // if statistic not collected,we add a big lag penalty to endpoint penalty = uint64(time.Microsecond * 100) ) var ( _ selector.WeightedNode = (*Node)(nil) _ selector.WeightedNodeBuilder = (*Builder)(nil) ) // Node is endpoint instance type Node struct { selector.Node // client statistic data lag atomic.Int64 success atomic.Uint64 inflight atomic.Int64 inflights [200]atomic.Int64 // last collected timestamp stamp atomic.Int64 // request number in a period time reqs atomic.Int64 // last lastPick timestamp lastPick atomic.Int64 errHandler func(err error) (isErr bool) cachedWeight *atomic.Value } type nodeWeight struct { value float64 updateAt int64 } // Builder is ewma node builder. type Builder struct { ErrHandler func(err error) (isErr bool) } // Build create a weighted node. func (b *Builder) Build(n selector.Node) selector.WeightedNode { s := &Node{ Node: n, inflights: [200]atomic.Int64{}, errHandler: b.ErrHandler, cachedWeight: &atomic.Value{}, } s.success.Store(1000) s.inflight.Store(1) return s } func (n *Node) health() uint64 { return n.success.Load() } func (n *Node) load() (load uint64) { now := time.Now().UnixNano() avgLag := n.lag.Load() predict := n.predict(avgLag, now) if avgLag == 0 { // penalty is the penalty value when there is no data when the node is just started. load = penalty * uint64(n.inflight.Load()) return } if predict > avgLag { avgLag = predict } // add 5ms to eliminate the latency gap between different zones avgLag += int64(time.Millisecond * 5) avgLag = int64(math.Sqrt(float64(avgLag))) load = uint64(avgLag) * uint64(n.inflight.Load()) return load } func (n *Node) predict(avgLag int64, now int64) (predict int64) { var ( total int64 slowNum int totalNum int ) for i := range n.inflights { start := n.inflights[i].Load() if start != 0 { totalNum++ lag := now - start if lag > avgLag { slowNum++ total += lag } } } if slowNum >= (totalNum/2 + 1) { predict = total / int64(slowNum) } return } // Pick pick a node. func (n *Node) Pick() selector.DoneFunc { start := time.Now().UnixNano() n.lastPick.Store(start) n.inflight.Add(1) reqs := n.reqs.Add(1) slot := reqs % 200 swapped := n.inflights[slot].CompareAndSwap(0, start) return func(_ context.Context, di selector.DoneInfo) { if swapped { n.inflights[slot].CompareAndSwap(start, 0) } n.inflight.Add(-1) now := time.Now().UnixNano() // get moving average ratio w stamp := n.stamp.Swap(now) td := now - stamp if td < 0 { td = 0 } w := math.Exp(float64(-td) / float64(tau)) lag := now - start if lag < 0 { lag = 0 } oldLag := n.lag.Load() if oldLag == 0 { w = 0.0 } lag = int64(float64(oldLag)*w + float64(lag)*(1.0-w)) n.lag.Store(lag) success := uint64(1000) // error value ,if error set 1 if di.Err != nil { if n.errHandler != nil && n.errHandler(di.Err) { success = 0 } var netErr net.Error if errors.Is(context.DeadlineExceeded, di.Err) || errors.Is(context.Canceled, di.Err) || errors.IsServiceUnavailable(di.Err) || errors.IsGatewayTimeout(di.Err) || errors.As(di.Err, &netErr) { success = 0 } } oldSuc := n.success.Load() success = uint64(float64(oldSuc)*w + float64(success)*(1.0-w)) n.success.Store(success) } } // Weight is node effective weight. func (n *Node) Weight() (weight float64) { w, ok := n.cachedWeight.Load().(*nodeWeight) now := time.Now().UnixNano() if !ok || time.Duration(now-w.updateAt) > (time.Millisecond*5) { health := n.health() load := n.load() weight = float64(health*uint64(time.Microsecond)*10) / float64(load) n.cachedWeight.Store(&nodeWeight{ value: weight, updateAt: now, }) } else { weight = w.value } return } func (n *Node) PickElapsed() time.Duration { return time.Duration(time.Now().UnixNano() - n.lastPick.Load()) } func (n *Node) Raw() selector.Node { return n.Node } ================================================ FILE: selector/node/ewma/node_test.go ================================================ package ewma import ( "context" "net" "reflect" "testing" "time" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) func TestDirect(t *testing.T) { b := &Builder{} wn := b.Build(selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) if !reflect.DeepEqual(float64(100), wn.Weight()) { t.Errorf("expect %v, got %v", 100, wn.Weight()) } done := wn.Pick() if done == nil { t.Errorf("done is equal to nil") } done2 := wn.Pick() if done2 == nil { t.Errorf("done2 is equal to nil") } time.Sleep(time.Millisecond * 15) done(context.Background(), selector.DoneInfo{}) if float64(70) >= wn.Weight() { t.Errorf("float64(30000) >= wn.Weight()(%v)", wn.Weight()) } if float64(1200) <= wn.Weight() { t.Errorf("float64(1000) <= wn.Weight()(%v)", wn.Weight()) } if time.Millisecond*30 <= wn.PickElapsed() { t.Errorf("time.Millisecond*30 <= wn.PickElapsed()(%v)", wn.PickElapsed()) } if time.Millisecond*5 >= wn.PickElapsed() { t.Errorf("time.Millisecond*5 >= wn.PickElapsed()(%v)", wn.PickElapsed()) } } func TestDirectError(t *testing.T) { b := &Builder{} wn := b.Build(selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) for i := 0; i < 5; i++ { var err error if i != 0 { err = context.DeadlineExceeded } done := wn.Pick() if done == nil { t.Errorf("expect not nil, got nil") } time.Sleep(time.Millisecond * 20) done(context.Background(), selector.DoneInfo{Err: err}) } if float64(1000) >= wn.Weight() { t.Errorf("float64(1000) >= wn.Weight()(%v)", wn.Weight()) } if float64(2000) <= wn.Weight() { t.Errorf("float64(2000) <= wn.Weight()(%v)", wn.Weight()) } } func TestDirectErrorHandler(t *testing.T) { b := &Builder{ ErrHandler: func(err error) bool { return err != nil }, } wn := b.Build(selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) errs := []error{ context.DeadlineExceeded, context.Canceled, net.ErrClosed, } for i := 0; i < 5; i++ { var err error if i != 0 { err = errs[i%len(errs)] } done := wn.Pick() if done == nil { t.Errorf("expect not nil, got nil") } time.Sleep(time.Millisecond * 20) done(context.Background(), selector.DoneInfo{Err: err}) } if float64(1000) >= wn.Weight() { t.Errorf("float64(100) >= wn.Weight()(%v)", wn.Weight()) } if float64(2000) <= wn.Weight() { t.Errorf("float64(200) <= wn.Weight()(%v)", wn.Weight()) } } func BenchmarkPickAndWeight(b *testing.B) { bu := &Builder{} node := bu.Build(selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) di := selector.DoneInfo{} b.RunParallel(func(pb *testing.PB) { for pb.Next() { done := node.Pick() node.Weight() done(context.Background(), di) } }) } ================================================ FILE: selector/options.go ================================================ package selector // SelectOptions is Select Options. type SelectOptions struct { NodeFilters []NodeFilter } // SelectOption is Selector option. type SelectOption func(*SelectOptions) // WithNodeFilter with filter options func WithNodeFilter(fn ...NodeFilter) SelectOption { return func(opts *SelectOptions) { opts.NodeFilters = fn } } ================================================ FILE: selector/p2c/p2c.go ================================================ package p2c import ( "context" "math/rand/v2" "sync" "sync/atomic" "time" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/node/ewma" ) const ( forcePick = time.Second * 3 // Name is p2c(Pick of 2 choices) balancer name Name = "p2c" ) var _ selector.Balancer = (*Balancer)(nil) // Option is p2c builder option. type Option func(o *options) // options is p2c builder options type options struct{} // New creates a p2c selector. func New(opts ...Option) selector.Selector { return NewBuilder(opts...).Build() } // Balancer is p2c selector. type Balancer struct { mu sync.Mutex r *rand.Rand picked atomic.Bool } // choose two distinct nodes. func (s *Balancer) prePick(nodes []selector.WeightedNode) (nodeA selector.WeightedNode, nodeB selector.WeightedNode) { s.mu.Lock() a := s.r.IntN(len(nodes)) b := s.r.IntN(len(nodes) - 1) s.mu.Unlock() if b >= a { b = b + 1 } nodeA, nodeB = nodes[a], nodes[b] return } // Pick pick a node. func (s *Balancer) Pick(_ context.Context, nodes []selector.WeightedNode) (selector.WeightedNode, selector.DoneFunc, error) { if len(nodes) == 0 { return nil, nil, selector.ErrNoAvailable } if len(nodes) == 1 { done := nodes[0].Pick() return nodes[0], done, nil } var pc, upc selector.WeightedNode nodeA, nodeB := s.prePick(nodes) // meta.Weight is the weight set by the service publisher in discovery if nodeB.Weight() > nodeA.Weight() { pc, upc = nodeB, nodeA } else { pc, upc = nodeA, nodeB } // If the failed node has never been selected once during forceGap, it is forced to be selected once // Take advantage of forced opportunities to trigger updates of success rate and delay if upc.PickElapsed() > forcePick && s.picked.CompareAndSwap(false, true) { defer s.picked.Store(false) pc = upc } done := pc.Pick() return pc, done, nil } // NewBuilder returns a selector builder with p2c balancer func NewBuilder(opts ...Option) selector.Builder { var option options for _, opt := range opts { opt(&option) } return &selector.DefaultBuilder{ Balancer: &Builder{}, Node: &ewma.Builder{}, } } // Builder is p2c builder type Builder struct{} // Build creates Balancer func (b *Builder) Build() selector.Balancer { return &Balancer{r: rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0))} } ================================================ FILE: selector/p2c/p2c_test.go ================================================ package p2c import ( "context" "fmt" "math/rand/v2" "reflect" "sync" "sync/atomic" "testing" "time" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/filter" ) func TestWrr3(t *testing.T) { p2c := New() var nodes []selector.Node for i := 0; i < 3; i++ { addr := fmt.Sprintf("127.0.0.%d:8080", i) nodes = append(nodes, selector.NewNode( "http", addr, ®istry.ServiceInstance{ ID: addr, Version: "v2.0.0", Metadata: map[string]string{"weight": "10"}, })) } p2c.Apply(nodes) var count1, count2, count3 int64 group := &sync.WaitGroup{} var lk sync.Mutex for i := 0; i < 9000; i++ { group.Add(1) go func() { defer group.Done() lk.Lock() d := time.Duration(rand.IntN(500)) * time.Millisecond lk.Unlock() time.Sleep(d) n, done, err := p2c.Select(context.Background(), selector.WithNodeFilter(filter.Version("v2.0.0"))) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if n == nil { t.Errorf("expect %v, got %v", nil, n) } if done == nil { t.Errorf("expect %v, got %v", nil, done) } time.Sleep(time.Millisecond * 10) done(context.Background(), selector.DoneInfo{}) if n.Address() == "127.0.0.0:8080" { atomic.AddInt64(&count1, 1) } else if n.Address() == "127.0.0.1:8080" { atomic.AddInt64(&count2, 1) } else if n.Address() == "127.0.0.2:8080" { atomic.AddInt64(&count3, 1) } }() } group.Wait() if count1 <= int64(1500) { t.Errorf("count1(%v) <= int64(1500)", count1) } if count1 >= int64(4500) { t.Errorf("count1(%v) >= int64(4500),", count1) } if count2 <= int64(1500) { t.Errorf("count2(%v) <= int64(1500)", count2) } if count2 >= int64(4500) { t.Errorf("count2(%v) >= int64(4500),", count2) } if count3 <= int64(1500) { t.Errorf("count3(%v) <= int64(1500)", count3) } if count3 >= int64(4500) { t.Errorf("count3(%v) >= int64(4500),", count3) } } func TestEmpty(t *testing.T) { b := &Balancer{} _, _, err := b.Pick(context.Background(), []selector.WeightedNode{}) if err == nil { t.Errorf("expect %v, got %v", nil, err) } } func TestOne(t *testing.T) { p2c := New() var nodes []selector.Node for i := 0; i < 1; i++ { addr := fmt.Sprintf("127.0.0.%d:8080", i) nodes = append(nodes, selector.NewNode( "http", addr, ®istry.ServiceInstance{ ID: addr, Version: "v2.0.0", Metadata: map[string]string{"weight": "10"}, })) } p2c.Apply(nodes) n, done, err := p2c.Select(context.Background(), selector.WithNodeFilter(filter.Version("v2.0.0"))) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if n == nil { t.Errorf("expect %v, got %v", nil, n) } if done == nil { t.Errorf("expect %v, got %v", nil, done) } if !reflect.DeepEqual("127.0.0.0:8080", n.Address()) { t.Errorf("expect %v, got %v", "127.0.0.0:8080", n.Address()) } } ================================================ FILE: selector/peer.go ================================================ package selector import ( "context" ) type peerKey struct{} // Peer contains the information of the peer for an RPC, such as the address // and authentication information. type Peer struct { // node is the peer node. Node Node } // NewPeerContext creates a new context with peer information attached. func NewPeerContext(ctx context.Context, p *Peer) context.Context { return context.WithValue(ctx, peerKey{}, p) } // FromPeerContext returns the peer information in ctx if it exists. func FromPeerContext(ctx context.Context) (p *Peer, ok bool) { p, ok = ctx.Value(peerKey{}).(*Peer) return } ================================================ FILE: selector/peer_test.go ================================================ package selector import ( "context" "testing" ) func TestPeer(t *testing.T) { p := Peer{ Node: mockWeightedNode{}, } ctx := NewPeerContext(context.Background(), &p) p2, ok := FromPeerContext(ctx) if !ok || p2.Node == nil { t.Fatalf(" no peer found!") } } func TestNotPeer(t *testing.T) { _, ok := FromPeerContext(context.Background()) if ok { t.Fatalf("test no peer found peer!") } } ================================================ FILE: selector/random/random.go ================================================ package random import ( "context" "math/rand/v2" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/node/direct" ) const ( // Name is random balancer name Name = "random" ) var _ selector.Balancer = (*Balancer)(nil) // Option is random builder option. type Option func(o *options) // options is random builder options type options struct{} // Balancer is a random balancer. type Balancer struct{} // New a random selector. func New(opts ...Option) selector.Selector { return NewBuilder(opts...).Build() } // Pick is pick a weighted node. func (p *Balancer) Pick(_ context.Context, nodes []selector.WeightedNode) (selector.WeightedNode, selector.DoneFunc, error) { if len(nodes) == 0 { return nil, nil, selector.ErrNoAvailable } cur := rand.IntN(len(nodes)) selected := nodes[cur] d := selected.Pick() return selected, d, nil } // NewBuilder returns a selector builder with random balancer func NewBuilder(opts ...Option) selector.Builder { var option options for _, opt := range opts { opt(&option) } return &selector.DefaultBuilder{ Balancer: &Builder{}, Node: &direct.Builder{}, } } // Builder is random builder type Builder struct{} // Build creates Balancer func (b *Builder) Build() selector.Balancer { return &Balancer{} } ================================================ FILE: selector/random/random_test.go ================================================ package random import ( "context" "testing" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/filter" ) func TestWrr(t *testing.T) { random := New() var nodes []selector.Node nodes = append(nodes, selector.NewNode( "http", "127.0.0.1:8080", ®istry.ServiceInstance{ ID: "127.0.0.1:8080", Version: "v2.0.0", Metadata: map[string]string{"weight": "10"}, })) nodes = append(nodes, selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Version: "v2.0.0", Metadata: map[string]string{"weight": "20"}, })) random.Apply(nodes) var count1, count2 int for i := 0; i < 1000; i++ { n, done, err := random.Select(context.Background(), selector.WithNodeFilter(filter.Version("v2.0.0"))) if err != nil { t.Errorf("expect no error, got %v", err) } if done == nil { t.Errorf("expect not nil, got:%v", done) } if n == nil { t.Errorf("expect not nil, got:%v", n) } done(context.Background(), selector.DoneInfo{}) if n.Address() == "127.0.0.1:8080" { count1++ } else if n.Address() == "127.0.0.1:9090" { count2++ } } if count1 <= 400 { t.Errorf("count1(%v) <= 400", count1) } if count1 >= 600 { t.Errorf("count1(%v) >= 600", count1) } if count2 <= 400 { t.Errorf("count2(%v) <= 400", count2) } if count2 >= 600 { t.Errorf("count2(%v) >= 600", count2) } } func TestEmpty(t *testing.T) { b := &Balancer{} _, _, err := b.Pick(context.Background(), []selector.WeightedNode{}) if err == nil { t.Errorf("expect nil, got %v", err) } } ================================================ FILE: selector/selector.go ================================================ package selector import ( "context" "github.com/go-kratos/kratos/v2/errors" ) // ErrNoAvailable is no available node. var ErrNoAvailable = errors.ServiceUnavailable("no_available_node", "") // Selector is node pick balancer. type Selector interface { Rebalancer // Select nodes // if err == nil, selected and done must not be empty. Select(ctx context.Context, opts ...SelectOption) (selected Node, done DoneFunc, err error) } // Rebalancer is nodes rebalancer. type Rebalancer interface { // Apply is apply all nodes when any changes happen Apply(nodes []Node) } // Builder build selector type Builder interface { Build() Selector } // Node is node interface. type Node interface { // Scheme is service node scheme Scheme() string // Address is the unique address under the same service Address() string // ServiceName is service name ServiceName() string // InitialWeight is the initial value of scheduling weight // if not set return nil InitialWeight() *int64 // Version is service node version Version() string // Metadata is the kv pair metadata associated with the service instance. // version,namespace,region,protocol etc.. Metadata() map[string]string } // DoneInfo is callback info when RPC invoke done. type DoneInfo struct { // Response Error Err error // Response Metadata ReplyMD ReplyMD // BytesSent indicates if any bytes have been sent to the server. BytesSent bool // BytesReceived indicates if any byte has been received from the server. BytesReceived bool } // ReplyMD is Reply Metadata. type ReplyMD interface { Get(key string) string } // DoneFunc is callback function when RPC invoke done. type DoneFunc func(ctx context.Context, di DoneInfo) ================================================ FILE: selector/selector_test.go ================================================ package selector import ( "context" "errors" "math/rand/v2" "reflect" "sync/atomic" "testing" "time" "github.com/go-kratos/kratos/v2/registry" ) var errNodeNotMatch = errors.New("node is not match") type mockWeightedNode struct { Node lastPick int64 } // Raw returns the original node func (n *mockWeightedNode) Raw() Node { return n.Node } // Weight is the runtime calculated weight func (n *mockWeightedNode) Weight() float64 { if n.InitialWeight() != nil { return float64(*n.InitialWeight()) } return 100 } // Pick the node func (n *mockWeightedNode) Pick() DoneFunc { now := time.Now().UnixNano() atomic.StoreInt64(&n.lastPick, now) return func(context.Context, DoneInfo) {} } // PickElapsed is time elapsed since the latest pick func (n *mockWeightedNode) PickElapsed() time.Duration { return time.Duration(time.Now().UnixNano() - atomic.LoadInt64(&n.lastPick)) } type mockWeightedNodeBuilder struct{} func (b *mockWeightedNodeBuilder) Build(n Node) WeightedNode { return &mockWeightedNode{Node: n} } func mockFilter(version string) NodeFilter { return func(_ context.Context, nodes []Node) []Node { newNodes := nodes[:0] for _, n := range nodes { if n.Version() == version { newNodes = append(newNodes, n) } } return newNodes } } type mockBalancerBuilder struct{} func (b *mockBalancerBuilder) Build() Balancer { return &mockBalancer{} } type mockBalancer struct{} func (b *mockBalancer) Pick(_ context.Context, nodes []WeightedNode) (selected WeightedNode, done DoneFunc, err error) { if len(nodes) == 0 { err = ErrNoAvailable return } cur := rand.IntN(len(nodes)) selected = nodes[cur] done = selected.Pick() return } type mockMustErrorBalancerBuilder struct{} func (b *mockMustErrorBalancerBuilder) Build() Balancer { return &mockMustErrorBalancer{} } type mockMustErrorBalancer struct{} func (b *mockMustErrorBalancer) Pick(_ context.Context, _ []WeightedNode) (selected WeightedNode, done DoneFunc, err error) { return nil, nil, errNodeNotMatch } func TestDefault(t *testing.T) { builder := DefaultBuilder{ Node: &mockWeightedNodeBuilder{}, Balancer: &mockBalancerBuilder{}, } selector := builder.Build() var nodes []Node nodes = append(nodes, NewNode( "http", "127.0.0.1:8080", ®istry.ServiceInstance{ ID: "127.0.0.1:8080", Name: "helloworld", Version: "v2.0.0", Endpoints: []string{"http://127.0.0.1:8080"}, Metadata: map[string]string{"weight": "10"}, })) nodes = append(nodes, NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) selector.Apply(nodes) n, done, err := selector.Select(context.Background(), WithNodeFilter(mockFilter("v2.0.0"))) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if n == nil { t.Errorf("expect %v, got %v", nil, n) } if done == nil { t.Errorf("expect %v, got %v", nil, done) } if !reflect.DeepEqual("v2.0.0", n.Version()) { t.Errorf("expect %v, got %v", "v2.0.0", n.Version()) } if n.Scheme() == "" { t.Errorf("expect %v, got %v", "", n.Scheme()) } if n.Address() == "" { t.Errorf("expect %v, got %v", "", n.Address()) } if !reflect.DeepEqual(int64(10), *n.InitialWeight()) { t.Errorf("expect %v, got %v", 10, *n.InitialWeight()) } if n.Metadata() == nil { t.Errorf("expect %v, got %v", nil, n.Metadata()) } if !reflect.DeepEqual("helloworld", n.ServiceName()) { t.Errorf("expect %v, got %v", "helloworld", n.ServiceName()) } done(context.Background(), DoneInfo{}) // peer in ctx ctx := NewPeerContext(context.Background(), &Peer{ Node: mockWeightedNode{}, }) n, done, err = selector.Select(ctx) if err != nil { t.Errorf("expect %v, got %v", ErrNoAvailable, err) } if done == nil { t.Errorf("expect %v, got %v", nil, done) } if n == nil { t.Errorf("expect %v, got %v", nil, n) } // no v3.0.0 instance n, done, err = selector.Select(context.Background(), WithNodeFilter(mockFilter("v3.0.0"))) if !errors.Is(ErrNoAvailable, err) { t.Errorf("expect %v, got %v", ErrNoAvailable, err) } if done != nil { t.Errorf("expect %v, got %v", nil, done) } if n != nil { t.Errorf("expect %v, got %v", nil, n) } // apply zero instance selector.Apply([]Node{}) n, done, err = selector.Select(context.Background(), WithNodeFilter(mockFilter("v2.0.0"))) if !errors.Is(ErrNoAvailable, err) { t.Errorf("expect %v, got %v", ErrNoAvailable, err) } if done != nil { t.Errorf("expect %v, got %v", nil, done) } if n != nil { t.Errorf("expect %v, got %v", nil, n) } // apply zero instance selector.Apply(nil) n, done, err = selector.Select(context.Background(), WithNodeFilter(mockFilter("v2.0.0"))) if !errors.Is(ErrNoAvailable, err) { t.Errorf("expect %v, got %v", ErrNoAvailable, err) } if done != nil { t.Errorf("expect %v, got %v", nil, done) } if n != nil { t.Errorf("expect %v, got %v", nil, n) } // without node_filters n, done, err = selector.Select(context.Background()) if !errors.Is(ErrNoAvailable, err) { t.Errorf("expect %v, got %v", ErrNoAvailable, err) } if done != nil { t.Errorf("expect %v, got %v", nil, done) } if n != nil { t.Errorf("expect %v, got %v", nil, n) } } func TestWithoutApply(t *testing.T) { builder := DefaultBuilder{ Node: &mockWeightedNodeBuilder{}, Balancer: &mockBalancerBuilder{}, } selector := builder.Build() n, done, err := selector.Select(context.Background()) if !errors.Is(ErrNoAvailable, err) { t.Errorf("expect %v, got %v", ErrNoAvailable, err) } if done != nil { t.Errorf("expect %v, got %v", nil, done) } if n != nil { t.Errorf("expect %v, got %v", nil, n) } } func TestNoPick(t *testing.T) { builder := DefaultBuilder{ Node: &mockWeightedNodeBuilder{}, Balancer: &mockMustErrorBalancerBuilder{}, } var nodes []Node nodes = append(nodes, NewNode( "http", "127.0.0.1:8080", ®istry.ServiceInstance{ ID: "127.0.0.1:8080", Name: "helloworld", Version: "v2.0.0", Endpoints: []string{"http://127.0.0.1:8080"}, Metadata: map[string]string{"weight": "10"}, })) nodes = append(nodes, NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Name: "helloworld", Version: "v1.0.0", Endpoints: []string{"http://127.0.0.1:9090"}, Metadata: map[string]string{"weight": "10"}, })) selector := builder.Build() selector.Apply(nodes) n, done, err := selector.Select(context.Background()) if !errors.Is(errNodeNotMatch, err) { t.Errorf("expect %v, got %v", errNodeNotMatch, err) } if done != nil { t.Errorf("expect %v, got %v", nil, done) } if n != nil { t.Errorf("expect %v, got %v", nil, n) } } func TestGlobalSelector(t *testing.T) { builder := DefaultBuilder{ Node: &mockWeightedNodeBuilder{}, Balancer: &mockBalancerBuilder{}, } SetGlobalSelector(&builder) gBuilder := GlobalSelector() if gBuilder == nil { t.Errorf("expect %v, got %v", nil, gBuilder) } } ================================================ FILE: selector/wrr/wrr.go ================================================ package wrr import ( "context" "sync" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/node/direct" ) const ( // Name is wrr(Weighted Round Robin) balancer name Name = "wrr" ) var _ selector.Balancer = (*Balancer)(nil) // Option is wrr builder option. type Option func(o *options) // options is wrr builder options type options struct{} // Balancer is a wrr balancer. type Balancer struct { mu sync.Mutex currentWeight map[string]float64 lastNodes []selector.WeightedNode } // equalNodes checks if two slices of WeightedNode contain the same nodes func equalNodes(a, b []selector.WeightedNode) bool { if len(a) != len(b) { return false } // Create a map of addresses from slice a aMap := make(map[string]bool, len(a)) for _, node := range a { aMap[node.Address()] = true } // Check if all nodes in slice b exist in slice a for _, node := range b { if !aMap[node.Address()] { return false } } return true } // New random a selector. func New(opts ...Option) selector.Selector { return NewBuilder(opts...).Build() } // Pick is pick a weighted node. func (p *Balancer) Pick(_ context.Context, nodes []selector.WeightedNode) (selector.WeightedNode, selector.DoneFunc, error) { if len(nodes) == 0 { return nil, nil, selector.ErrNoAvailable } p.mu.Lock() defer p.mu.Unlock() // Check if the node list has changed if !equalNodes(p.lastNodes, nodes) { // Update lastNodes p.lastNodes = make([]selector.WeightedNode, len(nodes)) copy(p.lastNodes, nodes) // Create a set of current node addresses for cleanup currentNodes := make(map[string]bool, len(nodes)) for _, node := range nodes { currentNodes[node.Address()] = true } // Clean up stale entries from currentWeight map for address := range p.currentWeight { if !currentNodes[address] { delete(p.currentWeight, address) } } } var totalWeight float64 var selected selector.WeightedNode var selectWeight float64 // nginx wrr load balancing algorithm: http://blog.csdn.net/zhangskd/article/details/50194069 for _, node := range nodes { totalWeight += node.Weight() cwt := p.currentWeight[node.Address()] // current += effectiveWeight cwt += node.Weight() p.currentWeight[node.Address()] = cwt if selected == nil || selectWeight < cwt { selectWeight = cwt selected = node } } p.currentWeight[selected.Address()] = selectWeight - totalWeight d := selected.Pick() return selected, d, nil } // NewBuilder returns a selector builder with wrr balancer func NewBuilder(opts ...Option) selector.Builder { var option options for _, opt := range opts { opt(&option) } return &selector.DefaultBuilder{ Balancer: &Builder{}, Node: &direct.Builder{}, } } // Builder is wrr builder type Builder struct{} // Build creates Balancer func (b *Builder) Build() selector.Balancer { return &Balancer{currentWeight: make(map[string]float64)} } ================================================ FILE: selector/wrr/wrr_test.go ================================================ package wrr import ( "context" "reflect" "testing" "time" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/filter" ) func TestWrr(t *testing.T) { wrr := New() var nodes []selector.Node nodes = append(nodes, selector.NewNode( "http", "127.0.0.1:8080", ®istry.ServiceInstance{ ID: "127.0.0.1:8080", Version: "v2.0.0", Metadata: map[string]string{"weight": "10"}, })) nodes = append(nodes, selector.NewNode( "http", "127.0.0.1:9090", ®istry.ServiceInstance{ ID: "127.0.0.1:9090", Version: "v2.0.0", Metadata: map[string]string{"weight": "20"}, })) wrr.Apply(nodes) var count1, count2 int for i := 0; i < 90; i++ { n, done, err := wrr.Select(context.Background(), selector.WithNodeFilter(filter.Version("v2.0.0"))) if err != nil { t.Errorf("expect no error, got %v", err) } if done == nil { t.Errorf("expect done callback, got nil") } if n == nil { t.Errorf("expect node, got nil") } done(context.Background(), selector.DoneInfo{}) if n.Address() == "127.0.0.1:8080" { count1++ } else if n.Address() == "127.0.0.1:9090" { count2++ } } if !reflect.DeepEqual(count1, 30) { t.Errorf("expect 30, got %d", count1) } if !reflect.DeepEqual(count2, 60) { t.Errorf("expect 60, got %d", count2) } } // TestCurrentWeightCleanup tests that stale entries in currentWeight map are cleaned up func TestCurrentWeightCleanup(t *testing.T) { balancer := &Balancer{currentWeight: make(map[string]float64)} // Create initial nodes nodes1 := []selector.WeightedNode{ &mockWeightedNode{address: "node1", weight: 10}, &mockWeightedNode{address: "node2", weight: 20}, &mockWeightedNode{address: "node3", weight: 30}, } // Pick from initial nodes to populate currentWeight for i := 0; i < 10; i++ { _, _, err := balancer.Pick(context.Background(), nodes1) if err != nil { t.Fatalf("unexpected error: %v", err) } } // Verify all 3 nodes are in currentWeight map if len(balancer.currentWeight) != 3 { t.Errorf("expected 3 entries in currentWeight, got %d", len(balancer.currentWeight)) } // Change to different set of nodes (simulating service discovery update) nodes2 := []selector.WeightedNode{ &mockWeightedNode{address: "node2", weight: 20}, // only node2 remains &mockWeightedNode{address: "node4", weight: 40}, // node4 is new } // Pick from new nodes _, _, err := balancer.Pick(context.Background(), nodes2) if err != nil { t.Fatalf("unexpected error: %v", err) } // Verify that stale entries (node1, node3) are cleaned up if len(balancer.currentWeight) != 2 { t.Errorf("expected 2 entries in currentWeight after cleanup, got %d", len(balancer.currentWeight)) } // Verify that node2 and node4 are present, but node1 and node3 are not if _, exists := balancer.currentWeight["node1"]; exists { t.Error("stale entry node1 should have been cleaned up") } if _, exists := balancer.currentWeight["node3"]; exists { t.Error("stale entry node3 should have been cleaned up") } if _, exists := balancer.currentWeight["node2"]; !exists { t.Error("node2 should be present in currentWeight") } if _, exists := balancer.currentWeight["node4"]; !exists { t.Error("node4 should be present in currentWeight") } } // TestCleanupOnlyWhenNodesChange verifies that cleanup logic only runs when nodes actually change func TestCleanupOnlyWhenNodesChange(t *testing.T) { // Create a custom balancer that tracks cleanup calls type trackingBalancer struct { *Balancer cleanupCount int } // Override the Pick method to count cleanup operations balancer := &trackingBalancer{ Balancer: &Balancer{currentWeight: make(map[string]float64)}, } originalPick := func(_ context.Context, nodes []selector.WeightedNode) (selector.WeightedNode, selector.DoneFunc, error) { if len(nodes) == 0 { return nil, nil, selector.ErrNoAvailable } balancer.mu.Lock() defer balancer.mu.Unlock() // Check if the node list has changed if len(balancer.lastNodes) != len(nodes) || !equalNodes(balancer.lastNodes, nodes) { balancer.cleanupCount++ // Count cleanup operations // Update lastNodes balancer.lastNodes = make([]selector.WeightedNode, len(nodes)) copy(balancer.lastNodes, nodes) // Create a set of current node addresses for cleanup currentNodes := make(map[string]bool) for _, node := range nodes { currentNodes[node.Address()] = true } // Clean up stale entries from currentWeight map for address := range balancer.currentWeight { if !currentNodes[address] { delete(balancer.currentWeight, address) } } } var totalWeight float64 var selected selector.WeightedNode var selectWeight float64 // nginx wrr load balancing algorithm for _, node := range nodes { totalWeight += node.Weight() cwt := balancer.currentWeight[node.Address()] cwt += node.Weight() balancer.currentWeight[node.Address()] = cwt if selected == nil || selectWeight < cwt { selectWeight = cwt selected = node } } balancer.currentWeight[selected.Address()] = selectWeight - totalWeight d := selected.Pick() return selected, d, nil } ctx := context.Background() nodes1 := []selector.WeightedNode{ &mockWeightedNode{address: "node1", weight: 10}, &mockWeightedNode{address: "node2", weight: 20}, } nodes2 := []selector.WeightedNode{ &mockWeightedNode{address: "node3", weight: 30}, &mockWeightedNode{address: "node4", weight: 40}, } var err error // First call with nodes1 - should trigger cleanup (initialization) _, _, err = originalPick(ctx, nodes1) if err != nil { t.Fatalf("unexpected error: %v", err) } if balancer.cleanupCount != 1 { t.Errorf("expected 1 cleanup call after first pick, got %d", balancer.cleanupCount) } // Multiple calls with same nodes1 - should NOT trigger additional cleanup for i := 0; i < 5; i++ { _, _, err = originalPick(ctx, nodes1) if err != nil { t.Fatalf("unexpected error: %v", err) } } if balancer.cleanupCount != 1 { t.Errorf("expected still 1 cleanup call after repeated picks with same nodes, got %d", balancer.cleanupCount) } // Call with different nodes2 - should trigger cleanup _, _, err = originalPick(ctx, nodes2) if err != nil { t.Fatalf("unexpected error: %v", err) } if balancer.cleanupCount != 2 { t.Errorf("expected 2 cleanup calls after node change, got %d", balancer.cleanupCount) } // Multiple calls with same nodes2 - should NOT trigger additional cleanup for i := 0; i < 3; i++ { _, _, err = originalPick(ctx, nodes2) if err != nil { t.Fatalf("unexpected error: %v", err) } } if balancer.cleanupCount != 2 { t.Errorf("expected still 2 cleanup calls after repeated picks with same nodes, got %d", balancer.cleanupCount) } } // mockWeightedNode is a mock implementation for testing type mockWeightedNode struct { address string weight float64 } func (m *mockWeightedNode) Raw() selector.Node { return nil } func (m *mockWeightedNode) Weight() float64 { return m.weight } func (m *mockWeightedNode) Address() string { return m.address } func (m *mockWeightedNode) Pick() selector.DoneFunc { return func(context.Context, selector.DoneInfo) {} } func (m *mockWeightedNode) PickElapsed() time.Duration { return 0 } func (m *mockWeightedNode) Scheme() string { return "http" } func (m *mockWeightedNode) ServiceName() string { return "test" } func (m *mockWeightedNode) InitialWeight() *int64 { return nil } func (m *mockWeightedNode) Version() string { return "v1.0.0" } func (m *mockWeightedNode) Metadata() map[string]string { return nil } func TestEmpty(t *testing.T) { b := &Balancer{} _, _, err := b.Pick(context.Background(), []selector.WeightedNode{}) if err == nil { t.Errorf("expect no error, got %v", err) } } // BenchmarkPickWithSameNodes benchmarks Pick() calls with the same node set // This demonstrates the performance improvement where cleanup only happens on node changes func BenchmarkPickWithSameNodes(b *testing.B) { balancer := &Balancer{currentWeight: make(map[string]float64)} // Create a fixed set of nodes nodes := []selector.WeightedNode{ &mockWeightedNode{address: "node1", weight: 10}, &mockWeightedNode{address: "node2", weight: 20}, &mockWeightedNode{address: "node3", weight: 30}, &mockWeightedNode{address: "node4", weight: 40}, &mockWeightedNode{address: "node5", weight: 50}, } ctx := context.Background() b.ResetTimer() b.ReportAllocs() // Benchmark Pick() calls with the same nodes // After the first call, no cleanup should occur on subsequent calls for i := 0; i < b.N; i++ { _, _, err := balancer.Pick(ctx, nodes) if err != nil { b.Fatalf("unexpected error: %v", err) } } } // BenchmarkPickWithChangingNodes benchmarks Pick() calls with changing node sets // This shows the overhead when nodes actually change (expected to be slower) func BenchmarkPickWithChangingNodes(b *testing.B) { balancer := &Balancer{currentWeight: make(map[string]float64)} // Create alternating sets of nodes to simulate node changes nodes1 := []selector.WeightedNode{ &mockWeightedNode{address: "node1", weight: 10}, &mockWeightedNode{address: "node2", weight: 20}, } nodes2 := []selector.WeightedNode{ &mockWeightedNode{address: "node3", weight: 30}, &mockWeightedNode{address: "node4", weight: 40}, } ctx := context.Background() b.ResetTimer() b.ReportAllocs() // Benchmark Pick() calls with alternating node sets // This will trigger cleanup on every call for i := 0; i < b.N; i++ { var nodes []selector.WeightedNode if i%2 == 0 { nodes = nodes1 } else { nodes = nodes2 } _, _, err := balancer.Pick(ctx, nodes) if err != nil { b.Fatalf("unexpected error: %v", err) } } } ================================================ FILE: third_party/README.md ================================================ # third_party ================================================ FILE: third_party/buf/validate/README.md ================================================ # protovalidate * https://github.com/bufbuild/protovalidate ================================================ FILE: third_party/buf/validate/validate.proto ================================================ // Copyright 2023 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto2"; package buf.validate; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"; option java_multiple_files = true; option java_outer_classname = "ValidateProto"; option java_package = "build.buf.validate"; // MessageOptions is an extension to google.protobuf.MessageOptions. It allows // the addition of validation rules at the message level. These rules can be // applied to incoming messages to ensure they meet certain criteria before // being processed. extend google.protobuf.MessageOptions { // Rules specify the validations to be performed on this message. By default, // no validation is performed against a message. optional MessageConstraints message = 1159; } // OneofOptions is an extension to google.protobuf.OneofOptions. It allows // the addition of validation rules on a oneof. These rules can be // applied to incoming messages to ensure they meet certain criteria before // being processed. extend google.protobuf.OneofOptions { // Rules specify the validations to be performed on this oneof. By default, // no validation is performed against a oneof. optional OneofConstraints oneof = 1159; } // FieldOptions is an extension to google.protobuf.FieldOptions. It allows // the addition of validation rules at the field level. These rules can be // applied to incoming messages to ensure they meet certain criteria before // being processed. extend google.protobuf.FieldOptions { // Rules specify the validations to be performed on this field. By default, // no validation is performed against a field. optional FieldConstraints field = 1159; // Specifies predefined rules. When extending a standard constraint message, // this adds additional CEL expressions that apply when the extension is used. // // ```proto // extend buf.validate.Int32Rules { // bool is_zero [(buf.validate.predefined).cel = { // id: "int32.is_zero", // message: "value must be zero", // expression: "!rule || this == 0", // }]; // } // // message Foo { // int32 reserved = 1 [(buf.validate.field).int32.(is_zero) = true]; // } // ``` optional PredefinedConstraints predefined = 1160; } // `Constraint` represents a validation rule written in the Common Expression // Language (CEL) syntax. Each Constraint includes a unique identifier, an // optional error message, and the CEL expression to evaluate. For more // information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). // // ```proto // message Foo { // option (buf.validate.message).cel = { // id: "foo.bar" // message: "bar must be greater than 0" // expression: "this.bar > 0" // }; // int32 bar = 1; // } // ``` message Constraint { // `id` is a string that serves as a machine-readable name for this Constraint. // It should be unique within its scope, which could be either a message or a field. optional string id = 1; // `message` is an optional field that provides a human-readable error message // for this Constraint when the CEL expression evaluates to false. If a // non-empty message is provided, any strings resulting from the CEL // expression evaluation are ignored. optional string message = 2; // `expression` is the actual CEL expression that will be evaluated for // validation. This string must resolve to either a boolean or a string // value. If the expression evaluates to false or a non-empty string, the // validation is considered failed, and the message is rejected. optional string expression = 3; } // MessageConstraints represents validation rules that are applied to the entire message. // It includes disabling options and a list of Constraint messages representing Common Expression Language (CEL) validation rules. message MessageConstraints { // `disabled` is a boolean flag that, when set to true, nullifies any validation rules for this message. // This includes any fields within the message that would otherwise support validation. // // ```proto // message MyMessage { // // validation will be bypassed for this message // option (buf.validate.message).disabled = true; // } // ``` optional bool disabled = 1; // `cel` is a repeated field of type Constraint. Each Constraint specifies a validation rule to be applied to this message. // These constraints are written in Common Expression Language (CEL) syntax. For more information on // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). // // // ```proto // message MyMessage { // // The field `foo` must be greater than 42. // option (buf.validate.message).cel = { // id: "my_message.value", // message: "value must be greater than 42", // expression: "this.foo > 42", // }; // optional int32 foo = 1; // } // ``` repeated Constraint cel = 3; } // The `OneofConstraints` message type enables you to manage constraints for // oneof fields in your protobuf messages. message OneofConstraints { // If `required` is true, exactly one field of the oneof must be present. A // validation error is returned if no fields in the oneof are present. The // field itself may still be a default value; further constraints // should be placed on the fields themselves to ensure they are valid values, // such as `min_len` or `gt`. // // ```proto // message MyMessage { // oneof value { // // Either `a` or `b` must be set. If `a` is set, it must also be // // non-empty; whereas if `b` is set, it can still be an empty string. // option (buf.validate.oneof).required = true; // string a = 1 [(buf.validate.field).string.min_len = 1]; // string b = 2; // } // } // ``` optional bool required = 1; } // FieldConstraints encapsulates the rules for each type of field. Depending on // the field, the correct set should be used to ensure proper validations. message FieldConstraints { // `cel` is a repeated field used to represent a textual expression // in the Common Expression Language (CEL) syntax. For more information on // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). // // ```proto // message MyMessage { // // The field `value` must be greater than 42. // optional int32 value = 1 [(buf.validate.field).cel = { // id: "my_message.value", // message: "value must be greater than 42", // expression: "this > 42", // }]; // } // ``` repeated Constraint cel = 23; // If `required` is true, the field must be populated. A populated field can be // described as "serialized in the wire format," which includes: // // - the following "nullable" fields must be explicitly set to be considered populated: // - singular message fields (whose fields may be unpopulated/default values) // - member fields of a oneof (may be their default value) // - proto3 optional fields (may be their default value) // - proto2 scalar fields (both optional and required) // - proto3 scalar fields must be non-zero to be considered populated // - repeated and map fields must be non-empty to be considered populated // // ```proto // message MyMessage { // // The field `value` must be set to a non-null value. // optional MyOtherMessage value = 1 [(buf.validate.field).required = true]; // } // ``` optional bool required = 25; // Skip validation on the field if its value matches the specified criteria. // See Ignore enum for details. // // ```proto // message UpdateRequest { // // The uri rule only applies if the field is populated and not an empty // // string. // optional string url = 1 [ // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE, // (buf.validate.field).string.uri = true, // ]; // } // ``` optional Ignore ignore = 27; oneof type { // Scalar Field Types FloatRules float = 1; DoubleRules double = 2; Int32Rules int32 = 3; Int64Rules int64 = 4; UInt32Rules uint32 = 5; UInt64Rules uint64 = 6; SInt32Rules sint32 = 7; SInt64Rules sint64 = 8; Fixed32Rules fixed32 = 9; Fixed64Rules fixed64 = 10; SFixed32Rules sfixed32 = 11; SFixed64Rules sfixed64 = 12; BoolRules bool = 13; StringRules string = 14; BytesRules bytes = 15; // Complex Field Types EnumRules enum = 16; RepeatedRules repeated = 18; MapRules map = 19; // Well-Known Field Types AnyRules any = 20; DurationRules duration = 21; TimestampRules timestamp = 22; } // DEPRECATED: use ignore=IGNORE_ALWAYS instead. TODO: remove this field pre-v1. optional bool skipped = 24 [deprecated = true]; // DEPRECATED: use ignore=IGNORE_IF_UNPOPULATED instead. TODO: remove this field pre-v1. optional bool ignore_empty = 26 [deprecated = true]; } // PredefinedConstraints are custom constraints that can be re-used with // multiple fields. message PredefinedConstraints { // `cel` is a repeated field used to represent a textual expression // in the Common Expression Language (CEL) syntax. For more information on // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). // // ```proto // message MyMessage { // // The field `value` must be greater than 42. // optional int32 value = 1 [(buf.validate.predefined).cel = { // id: "my_message.value", // message: "value must be greater than 42", // expression: "this > 42", // }]; // } // ``` repeated Constraint cel = 1; } // Specifies how FieldConstraints.ignore behaves. See the documentation for // FieldConstraints.required for definitions of "populated" and "nullable". enum Ignore { // buf:lint:ignore ENUM_NO_ALLOW_ALIAS // allowance for deprecations. TODO: remove pre-v1. option allow_alias = true; // Validation is only skipped if it's an unpopulated nullable fields. // // ```proto // syntax="proto3"; // // message Request { // // The uri rule applies to any value, including the empty string. // string foo = 1 [ // (buf.validate.field).string.uri = true // ]; // // // The uri rule only applies if the field is set, including if it's // // set to the empty string. // optional string bar = 2 [ // (buf.validate.field).string.uri = true // ]; // // // The min_items rule always applies, even if the list is empty. // repeated string baz = 3 [ // (buf.validate.field).repeated.min_items = 3 // ]; // // // The custom CEL rule applies only if the field is set, including if // // it's the "zero" value of that message. // SomeMessage quux = 4 [ // (buf.validate.field).cel = {/* ... */} // ]; // } // ``` IGNORE_UNSPECIFIED = 0; // Validation is skipped if the field is unpopulated. This rule is redundant // if the field is already nullable. This value is equivalent behavior to the // deprecated ignore_empty rule. // // ```proto // syntax="proto3 // // message Request { // // The uri rule applies only if the value is not the empty string. // string foo = 1 [ // (buf.validate.field).string.uri = true, // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED // ]; // // // IGNORE_IF_UNPOPULATED is equivalent to IGNORE_UNSPECIFIED in this // // case: the uri rule only applies if the field is set, including if // // it's set to the empty string. // optional string bar = 2 [ // (buf.validate.field).string.uri = true, // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED // ]; // // // The min_items rule only applies if the list has at least one item. // repeated string baz = 3 [ // (buf.validate.field).repeated.min_items = 3, // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED // ]; // // // IGNORE_IF_UNPOPULATED is equivalent to IGNORE_UNSPECIFIED in this // // case: the custom CEL rule applies only if the field is set, including // // if it's the "zero" value of that message. // SomeMessage quux = 4 [ // (buf.validate.field).cel = {/* ... */}, // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED // ]; // } // ``` IGNORE_IF_UNPOPULATED = 1; // Validation is skipped if the field is unpopulated or if it is a nullable // field populated with its default value. This is typically the zero or // empty value, but proto2 scalars support custom defaults. For messages, the // default is a non-null message with all its fields unpopulated. // // ```proto // syntax="proto3 // // message Request { // // IGNORE_IF_DEFAULT_VALUE is equivalent to IGNORE_IF_UNPOPULATED in // // this case; the uri rule applies only if the value is not the empty // // string. // string foo = 1 [ // (buf.validate.field).string.uri = true, // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE // ]; // // // The uri rule only applies if the field is set to a value other than // // the empty string. // optional string bar = 2 [ // (buf.validate.field).string.uri = true, // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE // ]; // // // IGNORE_IF_DEFAULT_VALUE is equivalent to IGNORE_IF_UNPOPULATED in // // this case; the min_items rule only applies if the list has at least // // one item. // repeated string baz = 3 [ // (buf.validate.field).repeated.min_items = 3, // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE // ]; // // // The custom CEL rule only applies if the field is set to a value other // // than an empty message (i.e., fields are unpopulated). // SomeMessage quux = 4 [ // (buf.validate.field).cel = {/* ... */}, // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE // ]; // } // ``` // // This rule is affected by proto2 custom default values: // // ```proto // syntax="proto2"; // // message Request { // // The gt rule only applies if the field is set and it's value is not // the default (i.e., not -42). The rule even applies if the field is set // to zero since the default value differs. // optional int32 value = 1 [ // default = -42, // (buf.validate.field).int32.gt = 0, // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE // ]; // } IGNORE_IF_DEFAULT_VALUE = 2; // The validation rules of this field will be skipped and not evaluated. This // is useful for situations that necessitate turning off the rules of a field // containing a message that may not make sense in the current context, or to // temporarily disable constraints during development. // // ```proto // message MyMessage { // // The field's rules will always be ignored, including any validation's // // on value's fields. // MyOtherMessage value = 1 [ // (buf.validate.field).ignore = IGNORE_ALWAYS]; // } // ``` IGNORE_ALWAYS = 3; // Deprecated: Use IGNORE_IF_UNPOPULATED instead. TODO: Remove this value pre-v1. IGNORE_EMPTY = 1 [deprecated = true]; // Deprecated: Use IGNORE_IF_DEFAULT_VALUE. TODO: Remove this value pre-v1. IGNORE_DEFAULT = 2 [deprecated = true]; } // FloatRules describes the constraints applied to `float` values. These // rules may also be applied to the `google.protobuf.FloatValue` Well-Known-Type. message FloatRules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyFloat { // // value must equal 42.0 // float value = 1 [(buf.validate.field).float.const = 42.0]; // } // ``` optional float const = 1 [(predefined).cel = { id: "float.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MyFloat { // // value must be less than 10.0 // float value = 1 [(buf.validate.field).float.lt = 10.0]; // } // ``` float lt = 2 [(predefined).cel = { id: "float.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyFloat { // // value must be less than or equal to 10.0 // float value = 1 [(buf.validate.field).float.lte = 10.0]; // } // ``` float lte = 3 [(predefined).cel = { id: "float.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyFloat { // // value must be greater than 5.0 [float.gt] // float value = 1 [(buf.validate.field).float.gt = 5.0]; // // // value must be greater than 5 and less than 10.0 [float.gt_lt] // float other_value = 2 [(buf.validate.field).float = { gt: 5.0, lt: 10.0 }]; // // // value must be greater than 10 or less than 5.0 [float.gt_lt_exclusive] // float another_value = 3 [(buf.validate.field).float = { gt: 10.0, lt: 5.0 }]; // } // ``` float gt = 4 [ (predefined).cel = { id: "float.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "float.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "float.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "float.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "float.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyFloat { // // value must be greater than or equal to 5.0 [float.gte] // float value = 1 [(buf.validate.field).float.gte = 5.0]; // // // value must be greater than or equal to 5.0 and less than 10.0 [float.gte_lt] // float other_value = 2 [(buf.validate.field).float = { gte: 5.0, lt: 10.0 }]; // // // value must be greater than or equal to 10.0 or less than 5.0 [float.gte_lt_exclusive] // float another_value = 3 [(buf.validate.field).float = { gte: 10.0, lt: 5.0 }]; // } // ``` float gte = 5 [ (predefined).cel = { id: "float.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "float.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "float.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "float.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "float.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message // is generated. // // ```proto // message MyFloat { // // value must be in list [1.0, 2.0, 3.0] // repeated float value = 1 (buf.validate.field).float = { in: [1.0, 2.0, 3.0] }; // } // ``` repeated float in = 6 [(predefined).cel = { id: "float.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyFloat { // // value must not be in list [1.0, 2.0, 3.0] // repeated float value = 1 (buf.validate.field).float = { not_in: [1.0, 2.0, 3.0] }; // } // ``` repeated float not_in = 7 [(predefined).cel = { id: "float.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. optional bool finite = 8 [(predefined).cel = { id: "float.finite" expression: "rules.finite ? (this.isNan() || this.isInf() ? 'value must be finite' : '') : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyFloat { // float value = 1 [ // (buf.validate.field).float.example = 1.0, // (buf.validate.field).float.example = "Infinity" // ]; // } // ``` repeated float example = 9 [(predefined).cel = { id: "float.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // DoubleRules describes the constraints applied to `double` values. These // rules may also be applied to the `google.protobuf.DoubleValue` Well-Known-Type. message DoubleRules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyDouble { // // value must equal 42.0 // double value = 1 [(buf.validate.field).double.const = 42.0]; // } // ``` optional double const = 1 [(predefined).cel = { id: "double.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified // value, an error message is generated. // // ```proto // message MyDouble { // // value must be less than 10.0 // double value = 1 [(buf.validate.field).double.lt = 10.0]; // } // ``` double lt = 2 [(predefined).cel = { id: "double.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified value // (field <= value). If the field value is greater than the specified value, // an error message is generated. // // ```proto // message MyDouble { // // value must be less than or equal to 10.0 // double value = 1 [(buf.validate.field).double.lte = 10.0]; // } // ``` double lte = 3 [(predefined).cel = { id: "double.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or `lte`, // the range is reversed, and the field value must be outside the specified // range. If the field value doesn't meet the required conditions, an error // message is generated. // // ```proto // message MyDouble { // // value must be greater than 5.0 [double.gt] // double value = 1 [(buf.validate.field).double.gt = 5.0]; // // // value must be greater than 5 and less than 10.0 [double.gt_lt] // double other_value = 2 [(buf.validate.field).double = { gt: 5.0, lt: 10.0 }]; // // // value must be greater than 10 or less than 5.0 [double.gt_lt_exclusive] // double another_value = 3 [(buf.validate.field).double = { gt: 10.0, lt: 5.0 }]; // } // ``` double gt = 4 [ (predefined).cel = { id: "double.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "double.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "double.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "double.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "double.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyDouble { // // value must be greater than or equal to 5.0 [double.gte] // double value = 1 [(buf.validate.field).double.gte = 5.0]; // // // value must be greater than or equal to 5.0 and less than 10.0 [double.gte_lt] // double other_value = 2 [(buf.validate.field).double = { gte: 5.0, lt: 10.0 }]; // // // value must be greater than or equal to 10.0 or less than 5.0 [double.gte_lt_exclusive] // double another_value = 3 [(buf.validate.field).double = { gte: 10.0, lt: 5.0 }]; // } // ``` double gte = 5 [ (predefined).cel = { id: "double.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "double.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "double.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "double.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "double.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MyDouble { // // value must be in list [1.0, 2.0, 3.0] // repeated double value = 1 (buf.validate.field).double = { in: [1.0, 2.0, 3.0] }; // } // ``` repeated double in = 6 [(predefined).cel = { id: "double.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyDouble { // // value must not be in list [1.0, 2.0, 3.0] // repeated double value = 1 (buf.validate.field).double = { not_in: [1.0, 2.0, 3.0] }; // } // ``` repeated double not_in = 7 [(predefined).cel = { id: "double.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. optional bool finite = 8 [(predefined).cel = { id: "double.finite" expression: "rules.finite ? (this.isNan() || this.isInf() ? 'value must be finite' : '') : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyDouble { // double value = 1 [ // (buf.validate.field).double.example = 1.0, // (buf.validate.field).double.example = "Infinity" // ]; // } // ``` repeated double example = 9 [(predefined).cel = { id: "double.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // Int32Rules describes the constraints applied to `int32` values. These // rules may also be applied to the `google.protobuf.Int32Value` Well-Known-Type. message Int32Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyInt32 { // // value must equal 42 // int32 value = 1 [(buf.validate.field).int32.const = 42]; // } // ``` optional int32 const = 1 [(predefined).cel = { id: "int32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field // < value). If the field value is equal to or greater than the specified // value, an error message is generated. // // ```proto // message MyInt32 { // // value must be less than 10 // int32 value = 1 [(buf.validate.field).int32.lt = 10]; // } // ``` int32 lt = 2 [(predefined).cel = { id: "int32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyInt32 { // // value must be less than or equal to 10 // int32 value = 1 [(buf.validate.field).int32.lte = 10]; // } // ``` int32 lte = 3 [(predefined).cel = { id: "int32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyInt32 { // // value must be greater than 5 [int32.gt] // int32 value = 1 [(buf.validate.field).int32.gt = 5]; // // // value must be greater than 5 and less than 10 [int32.gt_lt] // int32 other_value = 2 [(buf.validate.field).int32 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [int32.gt_lt_exclusive] // int32 another_value = 3 [(buf.validate.field).int32 = { gt: 10, lt: 5 }]; // } // ``` int32 gt = 4 [ (predefined).cel = { id: "int32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "int32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "int32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "int32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "int32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified value // (exclusive). If the value of `gte` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyInt32 { // // value must be greater than or equal to 5 [int32.gte] // int32 value = 1 [(buf.validate.field).int32.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [int32.gte_lt] // int32 other_value = 2 [(buf.validate.field).int32 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [int32.gte_lt_exclusive] // int32 another_value = 3 [(buf.validate.field).int32 = { gte: 10, lt: 5 }]; // } // ``` int32 gte = 5 [ (predefined).cel = { id: "int32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "int32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "int32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "int32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "int32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MyInt32 { // // value must be in list [1, 2, 3] // repeated int32 value = 1 (buf.validate.field).int32 = { in: [1, 2, 3] }; // } // ``` repeated int32 in = 6 [(predefined).cel = { id: "int32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error message // is generated. // // ```proto // message MyInt32 { // // value must not be in list [1, 2, 3] // repeated int32 value = 1 (buf.validate.field).int32 = { not_in: [1, 2, 3] }; // } // ``` repeated int32 not_in = 7 [(predefined).cel = { id: "int32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyInt32 { // int32 value = 1 [ // (buf.validate.field).int32.example = 1, // (buf.validate.field).int32.example = -10 // ]; // } // ``` repeated int32 example = 8 [(predefined).cel = { id: "int32.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // Int64Rules describes the constraints applied to `int64` values. These // rules may also be applied to the `google.protobuf.Int64Value` Well-Known-Type. message Int64Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyInt64 { // // value must equal 42 // int64 value = 1 [(buf.validate.field).int64.const = 42]; // } // ``` optional int64 const = 1 [(predefined).cel = { id: "int64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MyInt64 { // // value must be less than 10 // int64 value = 1 [(buf.validate.field).int64.lt = 10]; // } // ``` int64 lt = 2 [(predefined).cel = { id: "int64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyInt64 { // // value must be less than or equal to 10 // int64 value = 1 [(buf.validate.field).int64.lte = 10]; // } // ``` int64 lte = 3 [(predefined).cel = { id: "int64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyInt64 { // // value must be greater than 5 [int64.gt] // int64 value = 1 [(buf.validate.field).int64.gt = 5]; // // // value must be greater than 5 and less than 10 [int64.gt_lt] // int64 other_value = 2 [(buf.validate.field).int64 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [int64.gt_lt_exclusive] // int64 another_value = 3 [(buf.validate.field).int64 = { gt: 10, lt: 5 }]; // } // ``` int64 gt = 4 [ (predefined).cel = { id: "int64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "int64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "int64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "int64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "int64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyInt64 { // // value must be greater than or equal to 5 [int64.gte] // int64 value = 1 [(buf.validate.field).int64.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [int64.gte_lt] // int64 other_value = 2 [(buf.validate.field).int64 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [int64.gte_lt_exclusive] // int64 another_value = 3 [(buf.validate.field).int64 = { gte: 10, lt: 5 }]; // } // ``` int64 gte = 5 [ (predefined).cel = { id: "int64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "int64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "int64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "int64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "int64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MyInt64 { // // value must be in list [1, 2, 3] // repeated int64 value = 1 (buf.validate.field).int64 = { in: [1, 2, 3] }; // } // ``` repeated int64 in = 6 [(predefined).cel = { id: "int64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyInt64 { // // value must not be in list [1, 2, 3] // repeated int64 value = 1 (buf.validate.field).int64 = { not_in: [1, 2, 3] }; // } // ``` repeated int64 not_in = 7 [(predefined).cel = { id: "int64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyInt64 { // int64 value = 1 [ // (buf.validate.field).int64.example = 1, // (buf.validate.field).int64.example = -10 // ]; // } // ``` repeated int64 example = 9 [(predefined).cel = { id: "int64.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // UInt32Rules describes the constraints applied to `uint32` values. These // rules may also be applied to the `google.protobuf.UInt32Value` Well-Known-Type. message UInt32Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyUInt32 { // // value must equal 42 // uint32 value = 1 [(buf.validate.field).uint32.const = 42]; // } // ``` optional uint32 const = 1 [(predefined).cel = { id: "uint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MyUInt32 { // // value must be less than 10 // uint32 value = 1 [(buf.validate.field).uint32.lt = 10]; // } // ``` uint32 lt = 2 [(predefined).cel = { id: "uint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyUInt32 { // // value must be less than or equal to 10 // uint32 value = 1 [(buf.validate.field).uint32.lte = 10]; // } // ``` uint32 lte = 3 [(predefined).cel = { id: "uint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyUInt32 { // // value must be greater than 5 [uint32.gt] // uint32 value = 1 [(buf.validate.field).uint32.gt = 5]; // // // value must be greater than 5 and less than 10 [uint32.gt_lt] // uint32 other_value = 2 [(buf.validate.field).uint32 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [uint32.gt_lt_exclusive] // uint32 another_value = 3 [(buf.validate.field).uint32 = { gt: 10, lt: 5 }]; // } // ``` uint32 gt = 4 [ (predefined).cel = { id: "uint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "uint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "uint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "uint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "uint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyUInt32 { // // value must be greater than or equal to 5 [uint32.gte] // uint32 value = 1 [(buf.validate.field).uint32.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [uint32.gte_lt] // uint32 other_value = 2 [(buf.validate.field).uint32 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [uint32.gte_lt_exclusive] // uint32 another_value = 3 [(buf.validate.field).uint32 = { gte: 10, lt: 5 }]; // } // ``` uint32 gte = 5 [ (predefined).cel = { id: "uint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "uint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "uint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "uint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "uint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MyUInt32 { // // value must be in list [1, 2, 3] // repeated uint32 value = 1 (buf.validate.field).uint32 = { in: [1, 2, 3] }; // } // ``` repeated uint32 in = 6 [(predefined).cel = { id: "uint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyUInt32 { // // value must not be in list [1, 2, 3] // repeated uint32 value = 1 (buf.validate.field).uint32 = { not_in: [1, 2, 3] }; // } // ``` repeated uint32 not_in = 7 [(predefined).cel = { id: "uint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyUInt32 { // uint32 value = 1 [ // (buf.validate.field).uint32.example = 1, // (buf.validate.field).uint32.example = 10 // ]; // } // ``` repeated uint32 example = 8 [(predefined).cel = { id: "uint32.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // UInt64Rules describes the constraints applied to `uint64` values. These // rules may also be applied to the `google.protobuf.UInt64Value` Well-Known-Type. message UInt64Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyUInt64 { // // value must equal 42 // uint64 value = 1 [(buf.validate.field).uint64.const = 42]; // } // ``` optional uint64 const = 1 [(predefined).cel = { id: "uint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MyUInt64 { // // value must be less than 10 // uint64 value = 1 [(buf.validate.field).uint64.lt = 10]; // } // ``` uint64 lt = 2 [(predefined).cel = { id: "uint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyUInt64 { // // value must be less than or equal to 10 // uint64 value = 1 [(buf.validate.field).uint64.lte = 10]; // } // ``` uint64 lte = 3 [(predefined).cel = { id: "uint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyUInt64 { // // value must be greater than 5 [uint64.gt] // uint64 value = 1 [(buf.validate.field).uint64.gt = 5]; // // // value must be greater than 5 and less than 10 [uint64.gt_lt] // uint64 other_value = 2 [(buf.validate.field).uint64 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [uint64.gt_lt_exclusive] // uint64 another_value = 3 [(buf.validate.field).uint64 = { gt: 10, lt: 5 }]; // } // ``` uint64 gt = 4 [ (predefined).cel = { id: "uint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "uint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "uint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "uint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "uint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyUInt64 { // // value must be greater than or equal to 5 [uint64.gte] // uint64 value = 1 [(buf.validate.field).uint64.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [uint64.gte_lt] // uint64 other_value = 2 [(buf.validate.field).uint64 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [uint64.gte_lt_exclusive] // uint64 another_value = 3 [(buf.validate.field).uint64 = { gte: 10, lt: 5 }]; // } // ``` uint64 gte = 5 [ (predefined).cel = { id: "uint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "uint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "uint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "uint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "uint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MyUInt64 { // // value must be in list [1, 2, 3] // repeated uint64 value = 1 (buf.validate.field).uint64 = { in: [1, 2, 3] }; // } // ``` repeated uint64 in = 6 [(predefined).cel = { id: "uint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyUInt64 { // // value must not be in list [1, 2, 3] // repeated uint64 value = 1 (buf.validate.field).uint64 = { not_in: [1, 2, 3] }; // } // ``` repeated uint64 not_in = 7 [(predefined).cel = { id: "uint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyUInt64 { // uint64 value = 1 [ // (buf.validate.field).uint64.example = 1, // (buf.validate.field).uint64.example = -10 // ]; // } // ``` repeated uint64 example = 8 [(predefined).cel = { id: "uint64.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // SInt32Rules describes the constraints applied to `sint32` values. message SInt32Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MySInt32 { // // value must equal 42 // sint32 value = 1 [(buf.validate.field).sint32.const = 42]; // } // ``` optional sint32 const = 1 [(predefined).cel = { id: "sint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field // < value). If the field value is equal to or greater than the specified // value, an error message is generated. // // ```proto // message MySInt32 { // // value must be less than 10 // sint32 value = 1 [(buf.validate.field).sint32.lt = 10]; // } // ``` sint32 lt = 2 [(predefined).cel = { id: "sint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MySInt32 { // // value must be less than or equal to 10 // sint32 value = 1 [(buf.validate.field).sint32.lte = 10]; // } // ``` sint32 lte = 3 [(predefined).cel = { id: "sint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySInt32 { // // value must be greater than 5 [sint32.gt] // sint32 value = 1 [(buf.validate.field).sint32.gt = 5]; // // // value must be greater than 5 and less than 10 [sint32.gt_lt] // sint32 other_value = 2 [(buf.validate.field).sint32 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [sint32.gt_lt_exclusive] // sint32 another_value = 3 [(buf.validate.field).sint32 = { gt: 10, lt: 5 }]; // } // ``` sint32 gt = 4 [ (predefined).cel = { id: "sint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "sint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "sint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySInt32 { // // value must be greater than or equal to 5 [sint32.gte] // sint32 value = 1 [(buf.validate.field).sint32.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [sint32.gte_lt] // sint32 other_value = 2 [(buf.validate.field).sint32 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [sint32.gte_lt_exclusive] // sint32 another_value = 3 [(buf.validate.field).sint32 = { gte: 10, lt: 5 }]; // } // ``` sint32 gte = 5 [ (predefined).cel = { id: "sint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "sint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "sint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MySInt32 { // // value must be in list [1, 2, 3] // repeated sint32 value = 1 (buf.validate.field).sint32 = { in: [1, 2, 3] }; // } // ``` repeated sint32 in = 6 [(predefined).cel = { id: "sint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MySInt32 { // // value must not be in list [1, 2, 3] // repeated sint32 value = 1 (buf.validate.field).sint32 = { not_in: [1, 2, 3] }; // } // ``` repeated sint32 not_in = 7 [(predefined).cel = { id: "sint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MySInt32 { // sint32 value = 1 [ // (buf.validate.field).sint32.example = 1, // (buf.validate.field).sint32.example = -10 // ]; // } // ``` repeated sint32 example = 8 [(predefined).cel = { id: "sint32.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // SInt64Rules describes the constraints applied to `sint64` values. message SInt64Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MySInt64 { // // value must equal 42 // sint64 value = 1 [(buf.validate.field).sint64.const = 42]; // } // ``` optional sint64 const = 1 [(predefined).cel = { id: "sint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field // < value). If the field value is equal to or greater than the specified // value, an error message is generated. // // ```proto // message MySInt64 { // // value must be less than 10 // sint64 value = 1 [(buf.validate.field).sint64.lt = 10]; // } // ``` sint64 lt = 2 [(predefined).cel = { id: "sint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MySInt64 { // // value must be less than or equal to 10 // sint64 value = 1 [(buf.validate.field).sint64.lte = 10]; // } // ``` sint64 lte = 3 [(predefined).cel = { id: "sint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySInt64 { // // value must be greater than 5 [sint64.gt] // sint64 value = 1 [(buf.validate.field).sint64.gt = 5]; // // // value must be greater than 5 and less than 10 [sint64.gt_lt] // sint64 other_value = 2 [(buf.validate.field).sint64 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [sint64.gt_lt_exclusive] // sint64 another_value = 3 [(buf.validate.field).sint64 = { gt: 10, lt: 5 }]; // } // ``` sint64 gt = 4 [ (predefined).cel = { id: "sint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "sint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "sint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySInt64 { // // value must be greater than or equal to 5 [sint64.gte] // sint64 value = 1 [(buf.validate.field).sint64.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [sint64.gte_lt] // sint64 other_value = 2 [(buf.validate.field).sint64 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [sint64.gte_lt_exclusive] // sint64 another_value = 3 [(buf.validate.field).sint64 = { gte: 10, lt: 5 }]; // } // ``` sint64 gte = 5 [ (predefined).cel = { id: "sint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "sint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "sint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message // is generated. // // ```proto // message MySInt64 { // // value must be in list [1, 2, 3] // repeated sint64 value = 1 (buf.validate.field).sint64 = { in: [1, 2, 3] }; // } // ``` repeated sint64 in = 6 [(predefined).cel = { id: "sint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MySInt64 { // // value must not be in list [1, 2, 3] // repeated sint64 value = 1 (buf.validate.field).sint64 = { not_in: [1, 2, 3] }; // } // ``` repeated sint64 not_in = 7 [(predefined).cel = { id: "sint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MySInt64 { // sint64 value = 1 [ // (buf.validate.field).sint64.example = 1, // (buf.validate.field).sint64.example = -10 // ]; // } // ``` repeated sint64 example = 8 [(predefined).cel = { id: "sint64.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // Fixed32Rules describes the constraints applied to `fixed32` values. message Fixed32Rules { // `const` requires the field value to exactly match the specified value. // If the field value doesn't match, an error message is generated. // // ```proto // message MyFixed32 { // // value must equal 42 // fixed32 value = 1 [(buf.validate.field).fixed32.const = 42]; // } // ``` optional fixed32 const = 1 [(predefined).cel = { id: "fixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MyFixed32 { // // value must be less than 10 // fixed32 value = 1 [(buf.validate.field).fixed32.lt = 10]; // } // ``` fixed32 lt = 2 [(predefined).cel = { id: "fixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyFixed32 { // // value must be less than or equal to 10 // fixed32 value = 1 [(buf.validate.field).fixed32.lte = 10]; // } // ``` fixed32 lte = 3 [(predefined).cel = { id: "fixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyFixed32 { // // value must be greater than 5 [fixed32.gt] // fixed32 value = 1 [(buf.validate.field).fixed32.gt = 5]; // // // value must be greater than 5 and less than 10 [fixed32.gt_lt] // fixed32 other_value = 2 [(buf.validate.field).fixed32 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [fixed32.gt_lt_exclusive] // fixed32 another_value = 3 [(buf.validate.field).fixed32 = { gt: 10, lt: 5 }]; // } // ``` fixed32 gt = 4 [ (predefined).cel = { id: "fixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "fixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "fixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "fixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "fixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyFixed32 { // // value must be greater than or equal to 5 [fixed32.gte] // fixed32 value = 1 [(buf.validate.field).fixed32.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [fixed32.gte_lt] // fixed32 other_value = 2 [(buf.validate.field).fixed32 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [fixed32.gte_lt_exclusive] // fixed32 another_value = 3 [(buf.validate.field).fixed32 = { gte: 10, lt: 5 }]; // } // ``` fixed32 gte = 5 [ (predefined).cel = { id: "fixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "fixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "fixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "fixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "fixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message // is generated. // // ```proto // message MyFixed32 { // // value must be in list [1, 2, 3] // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { in: [1, 2, 3] }; // } // ``` repeated fixed32 in = 6 [(predefined).cel = { id: "fixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyFixed32 { // // value must not be in list [1, 2, 3] // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { not_in: [1, 2, 3] }; // } // ``` repeated fixed32 not_in = 7 [(predefined).cel = { id: "fixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyFixed32 { // fixed32 value = 1 [ // (buf.validate.field).fixed32.example = 1, // (buf.validate.field).fixed32.example = 2 // ]; // } // ``` repeated fixed32 example = 8 [(predefined).cel = { id: "fixed32.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // Fixed64Rules describes the constraints applied to `fixed64` values. message Fixed64Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyFixed64 { // // value must equal 42 // fixed64 value = 1 [(buf.validate.field).fixed64.const = 42]; // } // ``` optional fixed64 const = 1 [(predefined).cel = { id: "fixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MyFixed64 { // // value must be less than 10 // fixed64 value = 1 [(buf.validate.field).fixed64.lt = 10]; // } // ``` fixed64 lt = 2 [(predefined).cel = { id: "fixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MyFixed64 { // // value must be less than or equal to 10 // fixed64 value = 1 [(buf.validate.field).fixed64.lte = 10]; // } // ``` fixed64 lte = 3 [(predefined).cel = { id: "fixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyFixed64 { // // value must be greater than 5 [fixed64.gt] // fixed64 value = 1 [(buf.validate.field).fixed64.gt = 5]; // // // value must be greater than 5 and less than 10 [fixed64.gt_lt] // fixed64 other_value = 2 [(buf.validate.field).fixed64 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [fixed64.gt_lt_exclusive] // fixed64 another_value = 3 [(buf.validate.field).fixed64 = { gt: 10, lt: 5 }]; // } // ``` fixed64 gt = 4 [ (predefined).cel = { id: "fixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "fixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "fixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "fixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "fixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyFixed64 { // // value must be greater than or equal to 5 [fixed64.gte] // fixed64 value = 1 [(buf.validate.field).fixed64.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [fixed64.gte_lt] // fixed64 other_value = 2 [(buf.validate.field).fixed64 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [fixed64.gte_lt_exclusive] // fixed64 another_value = 3 [(buf.validate.field).fixed64 = { gte: 10, lt: 5 }]; // } // ``` fixed64 gte = 5 [ (predefined).cel = { id: "fixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "fixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "fixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "fixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "fixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MyFixed64 { // // value must be in list [1, 2, 3] // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { in: [1, 2, 3] }; // } // ``` repeated fixed64 in = 6 [(predefined).cel = { id: "fixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MyFixed64 { // // value must not be in list [1, 2, 3] // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { not_in: [1, 2, 3] }; // } // ``` repeated fixed64 not_in = 7 [(predefined).cel = { id: "fixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyFixed64 { // fixed64 value = 1 [ // (buf.validate.field).fixed64.example = 1, // (buf.validate.field).fixed64.example = 2 // ]; // } // ``` repeated fixed64 example = 8 [(predefined).cel = { id: "fixed64.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // SFixed32Rules describes the constraints applied to `fixed32` values. message SFixed32Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MySFixed32 { // // value must equal 42 // sfixed32 value = 1 [(buf.validate.field).sfixed32.const = 42]; // } // ``` optional sfixed32 const = 1 [(predefined).cel = { id: "sfixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MySFixed32 { // // value must be less than 10 // sfixed32 value = 1 [(buf.validate.field).sfixed32.lt = 10]; // } // ``` sfixed32 lt = 2 [(predefined).cel = { id: "sfixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MySFixed32 { // // value must be less than or equal to 10 // sfixed32 value = 1 [(buf.validate.field).sfixed32.lte = 10]; // } // ``` sfixed32 lte = 3 [(predefined).cel = { id: "sfixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySFixed32 { // // value must be greater than 5 [sfixed32.gt] // sfixed32 value = 1 [(buf.validate.field).sfixed32.gt = 5]; // // // value must be greater than 5 and less than 10 [sfixed32.gt_lt] // sfixed32 other_value = 2 [(buf.validate.field).sfixed32 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [sfixed32.gt_lt_exclusive] // sfixed32 another_value = 3 [(buf.validate.field).sfixed32 = { gt: 10, lt: 5 }]; // } // ``` sfixed32 gt = 4 [ (predefined).cel = { id: "sfixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "sfixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "sfixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySFixed32 { // // value must be greater than or equal to 5 [sfixed32.gte] // sfixed32 value = 1 [(buf.validate.field).sfixed32.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [sfixed32.gte_lt] // sfixed32 other_value = 2 [(buf.validate.field).sfixed32 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [sfixed32.gte_lt_exclusive] // sfixed32 another_value = 3 [(buf.validate.field).sfixed32 = { gte: 10, lt: 5 }]; // } // ``` sfixed32 gte = 5 [ (predefined).cel = { id: "sfixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "sfixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "sfixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MySFixed32 { // // value must be in list [1, 2, 3] // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { in: [1, 2, 3] }; // } // ``` repeated sfixed32 in = 6 [(predefined).cel = { id: "sfixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MySFixed32 { // // value must not be in list [1, 2, 3] // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { not_in: [1, 2, 3] }; // } // ``` repeated sfixed32 not_in = 7 [(predefined).cel = { id: "sfixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MySFixed32 { // sfixed32 value = 1 [ // (buf.validate.field).sfixed32.example = 1, // (buf.validate.field).sfixed32.example = 2 // ]; // } // ``` repeated sfixed32 example = 8 [(predefined).cel = { id: "sfixed32.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // SFixed64Rules describes the constraints applied to `fixed64` values. message SFixed64Rules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MySFixed64 { // // value must equal 42 // sfixed64 value = 1 [(buf.validate.field).sfixed64.const = 42]; // } // ``` optional sfixed64 const = 1 [(predefined).cel = { id: "sfixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` requires the field value to be less than the specified value (field < // value). If the field value is equal to or greater than the specified value, // an error message is generated. // // ```proto // message MySFixed64 { // // value must be less than 10 // sfixed64 value = 1 [(buf.validate.field).sfixed64.lt = 10]; // } // ``` sfixed64 lt = 2 [(predefined).cel = { id: "sfixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` requires the field value to be less than or equal to the specified // value (field <= value). If the field value is greater than the specified // value, an error message is generated. // // ```proto // message MySFixed64 { // // value must be less than or equal to 10 // sfixed64 value = 1 [(buf.validate.field).sfixed64.lte = 10]; // } // ``` sfixed64 lte = 3 [(predefined).cel = { id: "sfixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the field value to be greater than the specified value // (exclusive). If the value of `gt` is larger than a specified `lt` or // `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySFixed64 { // // value must be greater than 5 [sfixed64.gt] // sfixed64 value = 1 [(buf.validate.field).sfixed64.gt = 5]; // // // value must be greater than 5 and less than 10 [sfixed64.gt_lt] // sfixed64 other_value = 2 [(buf.validate.field).sfixed64 = { gt: 5, lt: 10 }]; // // // value must be greater than 10 or less than 5 [sfixed64.gt_lt_exclusive] // sfixed64 another_value = 3 [(buf.validate.field).sfixed64 = { gt: 10, lt: 5 }]; // } // ``` sfixed64 gt = 4 [ (predefined).cel = { id: "sfixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "sfixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "sfixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the field value to be greater than or equal to the specified // value (exclusive). If the value of `gte` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MySFixed64 { // // value must be greater than or equal to 5 [sfixed64.gte] // sfixed64 value = 1 [(buf.validate.field).sfixed64.gte = 5]; // // // value must be greater than or equal to 5 and less than 10 [sfixed64.gte_lt] // sfixed64 other_value = 2 [(buf.validate.field).sfixed64 = { gte: 5, lt: 10 }]; // // // value must be greater than or equal to 10 or less than 5 [sfixed64.gte_lt_exclusive] // sfixed64 another_value = 3 [(buf.validate.field).sfixed64 = { gte: 10, lt: 5 }]; // } // ``` sfixed64 gte = 5 [ (predefined).cel = { id: "sfixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "sfixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "sfixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "sfixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` requires the field value to be equal to one of the specified values. // If the field value isn't one of the specified values, an error message is // generated. // // ```proto // message MySFixed64 { // // value must be in list [1, 2, 3] // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { in: [1, 2, 3] }; // } // ``` repeated sfixed64 in = 6 [(predefined).cel = { id: "sfixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to not be equal to any of the specified // values. If the field value is one of the specified values, an error // message is generated. // // ```proto // message MySFixed64 { // // value must not be in list [1, 2, 3] // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { not_in: [1, 2, 3] }; // } // ``` repeated sfixed64 not_in = 7 [(predefined).cel = { id: "sfixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MySFixed64 { // sfixed64 value = 1 [ // (buf.validate.field).sfixed64.example = 1, // (buf.validate.field).sfixed64.example = 2 // ]; // } // ``` repeated sfixed64 example = 8 [(predefined).cel = { id: "sfixed64.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // BoolRules describes the constraints applied to `bool` values. These rules // may also be applied to the `google.protobuf.BoolValue` Well-Known-Type. message BoolRules { // `const` requires the field value to exactly match the specified boolean value. // If the field value doesn't match, an error message is generated. // // ```proto // message MyBool { // // value must equal true // bool value = 1 [(buf.validate.field).bool.const = true]; // } // ``` optional bool const = 1 [(predefined).cel = { id: "bool.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyBool { // bool value = 1 [ // (buf.validate.field).bool.example = 1, // (buf.validate.field).bool.example = 2 // ]; // } // ``` repeated bool example = 2 [(predefined).cel = { id: "bool.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // StringRules describes the constraints applied to `string` values These // rules may also be applied to the `google.protobuf.StringValue` Well-Known-Type. message StringRules { // `const` requires the field value to exactly match the specified value. If // the field value doesn't match, an error message is generated. // // ```proto // message MyString { // // value must equal `hello` // string value = 1 [(buf.validate.field).string.const = "hello"]; // } // ``` optional string const = 1 [(predefined).cel = { id: "string.const" expression: "this != rules.const ? 'value must equal `%s`'.format([rules.const]) : ''" }]; // `len` dictates that the field value must have the specified // number of characters (Unicode code points), which may differ from the number // of bytes in the string. If the field value does not meet the specified // length, an error message will be generated. // // ```proto // message MyString { // // value length must be 5 characters // string value = 1 [(buf.validate.field).string.len = 5]; // } // ``` optional uint64 len = 19 [(predefined).cel = { id: "string.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s characters'.format([rules.len]) : ''" }]; // `min_len` specifies that the field value must have at least the specified // number of characters (Unicode code points), which may differ from the number // of bytes in the string. If the field value contains fewer characters, an error // message will be generated. // // ```proto // message MyString { // // value length must be at least 3 characters // string value = 1 [(buf.validate.field).string.min_len = 3]; // } // ``` optional uint64 min_len = 2 [(predefined).cel = { id: "string.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s characters'.format([rules.min_len]) : ''" }]; // `max_len` specifies that the field value must have no more than the specified // number of characters (Unicode code points), which may differ from the // number of bytes in the string. If the field value contains more characters, // an error message will be generated. // // ```proto // message MyString { // // value length must be at most 10 characters // string value = 1 [(buf.validate.field).string.max_len = 10]; // } // ``` optional uint64 max_len = 3 [(predefined).cel = { id: "string.max_len" expression: "uint(this.size()) > rules.max_len ? 'value length must be at most %s characters'.format([rules.max_len]) : ''" }]; // `len_bytes` dictates that the field value must have the specified number of // bytes. If the field value does not match the specified length in bytes, // an error message will be generated. // // ```proto // message MyString { // // value length must be 6 bytes // string value = 1 [(buf.validate.field).string.len_bytes = 6]; // } // ``` optional uint64 len_bytes = 20 [(predefined).cel = { id: "string.len_bytes" expression: "uint(bytes(this).size()) != rules.len_bytes ? 'value length must be %s bytes'.format([rules.len_bytes]) : ''" }]; // `min_bytes` specifies that the field value must have at least the specified // number of bytes. If the field value contains fewer bytes, an error message // will be generated. // // ```proto // message MyString { // // value length must be at least 4 bytes // string value = 1 [(buf.validate.field).string.min_bytes = 4]; // } // // ``` optional uint64 min_bytes = 4 [(predefined).cel = { id: "string.min_bytes" expression: "uint(bytes(this).size()) < rules.min_bytes ? 'value length must be at least %s bytes'.format([rules.min_bytes]) : ''" }]; // `max_bytes` specifies that the field value must have no more than the //specified number of bytes. If the field value contains more bytes, an // error message will be generated. // // ```proto // message MyString { // // value length must be at most 8 bytes // string value = 1 [(buf.validate.field).string.max_bytes = 8]; // } // ``` optional uint64 max_bytes = 5 [(predefined).cel = { id: "string.max_bytes" expression: "uint(bytes(this).size()) > rules.max_bytes ? 'value length must be at most %s bytes'.format([rules.max_bytes]) : ''" }]; // `pattern` specifies that the field value must match the specified // regular expression (RE2 syntax), with the expression provided without any // delimiters. If the field value doesn't match the regular expression, an // error message will be generated. // // ```proto // message MyString { // // value does not match regex pattern `^[a-zA-Z]//$` // string value = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z]//$"]; // } // ``` optional string pattern = 6 [(predefined).cel = { id: "string.pattern" expression: "!this.matches(rules.pattern) ? 'value does not match regex pattern `%s`'.format([rules.pattern]) : ''" }]; // `prefix` specifies that the field value must have the //specified substring at the beginning of the string. If the field value // doesn't start with the specified prefix, an error message will be // generated. // // ```proto // message MyString { // // value does not have prefix `pre` // string value = 1 [(buf.validate.field).string.prefix = "pre"]; // } // ``` optional string prefix = 7 [(predefined).cel = { id: "string.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix `%s`'.format([rules.prefix]) : ''" }]; // `suffix` specifies that the field value must have the //specified substring at the end of the string. If the field value doesn't // end with the specified suffix, an error message will be generated. // // ```proto // message MyString { // // value does not have suffix `post` // string value = 1 [(buf.validate.field).string.suffix = "post"]; // } // ``` optional string suffix = 8 [(predefined).cel = { id: "string.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix `%s`'.format([rules.suffix]) : ''" }]; // `contains` specifies that the field value must have the //specified substring anywhere in the string. If the field value doesn't // contain the specified substring, an error message will be generated. // // ```proto // message MyString { // // value does not contain substring `inside`. // string value = 1 [(buf.validate.field).string.contains = "inside"]; // } // ``` optional string contains = 9 [(predefined).cel = { id: "string.contains" expression: "!this.contains(rules.contains) ? 'value does not contain substring `%s`'.format([rules.contains]) : ''" }]; // `not_contains` specifies that the field value must not have the //specified substring anywhere in the string. If the field value contains // the specified substring, an error message will be generated. // // ```proto // message MyString { // // value contains substring `inside`. // string value = 1 [(buf.validate.field).string.not_contains = "inside"]; // } // ``` optional string not_contains = 23 [(predefined).cel = { id: "string.not_contains" expression: "this.contains(rules.not_contains) ? 'value contains substring `%s`'.format([rules.not_contains]) : ''" }]; // `in` specifies that the field value must be equal to one of the specified // values. If the field value isn't one of the specified values, an error // message will be generated. // // ```proto // message MyString { // // value must be in list ["apple", "banana"] // repeated string value = 1 [(buf.validate.field).string.in = "apple", (buf.validate.field).string.in = "banana"]; // } // ``` repeated string in = 10 [(predefined).cel = { id: "string.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` specifies that the field value cannot be equal to any // of the specified values. If the field value is one of the specified values, // an error message will be generated. // ```proto // message MyString { // // value must not be in list ["orange", "grape"] // repeated string value = 1 [(buf.validate.field).string.not_in = "orange", (buf.validate.field).string.not_in = "grape"]; // } // ``` repeated string not_in = 11 [(predefined).cel = { id: "string.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `WellKnown` rules provide advanced constraints against common string // patterns oneof well_known { // `email` specifies that the field value must be a valid email address // (addr-spec only) as defined by [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.4.1). // If the field value isn't a valid email address, an error message will be generated. // // ```proto // message MyString { // // value must be a valid email address // string value = 1 [(buf.validate.field).string.email = true]; // } // ``` bool email = 12 [ (predefined).cel = { id: "string.email" message: "value must be a valid email address" expression: "!rules.email || this == '' || this.isEmail()" }, (predefined).cel = { id: "string.email_empty" message: "value is empty, which is not a valid email address" expression: "!rules.email || this != ''" } ]; // `hostname` specifies that the field value must be a valid // hostname as defined by [RFC 1034](https://tools.ietf.org/html/rfc1034#section-3.5). This constraint doesn't support // internationalized domain names (IDNs). If the field value isn't a // valid hostname, an error message will be generated. // // ```proto // message MyString { // // value must be a valid hostname // string value = 1 [(buf.validate.field).string.hostname = true]; // } // ``` bool hostname = 13 [ (predefined).cel = { id: "string.hostname" message: "value must be a valid hostname" expression: "!rules.hostname || this == '' || this.isHostname()" }, (predefined).cel = { id: "string.hostname_empty" message: "value is empty, which is not a valid hostname" expression: "!rules.hostname || this != ''" } ]; // `ip` specifies that the field value must be a valid IP // (v4 or v6) address, without surrounding square brackets for IPv6 addresses. // If the field value isn't a valid IP address, an error message will be // generated. // // ```proto // message MyString { // // value must be a valid IP address // string value = 1 [(buf.validate.field).string.ip = true]; // } // ``` bool ip = 14 [ (predefined).cel = { id: "string.ip" message: "value must be a valid IP address" expression: "!rules.ip || this == '' || this.isIp()" }, (predefined).cel = { id: "string.ip_empty" message: "value is empty, which is not a valid IP address" expression: "!rules.ip || this != ''" } ]; // `ipv4` specifies that the field value must be a valid IPv4 // address. If the field value isn't a valid IPv4 address, an error message // will be generated. // // ```proto // message MyString { // // value must be a valid IPv4 address // string value = 1 [(buf.validate.field).string.ipv4 = true]; // } // ``` bool ipv4 = 15 [ (predefined).cel = { id: "string.ipv4" message: "value must be a valid IPv4 address" expression: "!rules.ipv4 || this == '' || this.isIp(4)" }, (predefined).cel = { id: "string.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" expression: "!rules.ipv4 || this != ''" } ]; // `ipv6` specifies that the field value must be a valid // IPv6 address, without surrounding square brackets. If the field value is // not a valid IPv6 address, an error message will be generated. // // ```proto // message MyString { // // value must be a valid IPv6 address // string value = 1 [(buf.validate.field).string.ipv6 = true]; // } // ``` bool ipv6 = 16 [ (predefined).cel = { id: "string.ipv6" message: "value must be a valid IPv6 address" expression: "!rules.ipv6 || this == '' || this.isIp(6)" }, (predefined).cel = { id: "string.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" expression: "!rules.ipv6 || this != ''" } ]; // `uri` specifies that the field value must be a valid, // absolute URI as defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3). If the field value isn't a valid, // absolute URI, an error message will be generated. // // ```proto // message MyString { // // value must be a valid URI // string value = 1 [(buf.validate.field).string.uri = true]; // } // ``` bool uri = 17 [ (predefined).cel = { id: "string.uri" message: "value must be a valid URI" expression: "!rules.uri || this == '' || this.isUri()" }, (predefined).cel = { id: "string.uri_empty" message: "value is empty, which is not a valid URI" expression: "!rules.uri || this != ''" } ]; // `uri_ref` specifies that the field value must be a valid URI // as defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3) and may be either relative or absolute. If the // field value isn't a valid URI, an error message will be generated. // // ```proto // message MyString { // // value must be a valid URI // string value = 1 [(buf.validate.field).string.uri_ref = true]; // } // ``` bool uri_ref = 18 [(predefined).cel = { id: "string.uri_ref" message: "value must be a valid URI" expression: "!rules.uri_ref || this.isUriRef()" }]; // `address` specifies that the field value must be either a valid hostname // as defined by [RFC 1034](https://tools.ietf.org/html/rfc1034#section-3.5) // (which doesn't support internationalized domain names or IDNs) or a valid // IP (v4 or v6). If the field value isn't a valid hostname or IP, an error // message will be generated. // // ```proto // message MyString { // // value must be a valid hostname, or ip address // string value = 1 [(buf.validate.field).string.address = true]; // } // ``` bool address = 21 [ (predefined).cel = { id: "string.address" message: "value must be a valid hostname, or ip address" expression: "!rules.address || this == '' || this.isHostname() || this.isIp()" }, (predefined).cel = { id: "string.address_empty" message: "value is empty, which is not a valid hostname, or ip address" expression: "!rules.address || this != ''" } ]; // `uuid` specifies that the field value must be a valid UUID as defined by // [RFC 4122](https://tools.ietf.org/html/rfc4122#section-4.1.2). If the // field value isn't a valid UUID, an error message will be generated. // // ```proto // message MyString { // // value must be a valid UUID // string value = 1 [(buf.validate.field).string.uuid = true]; // } // ``` bool uuid = 22 [ (predefined).cel = { id: "string.uuid" message: "value must be a valid UUID" expression: "!rules.uuid || this == '' || this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')" }, (predefined).cel = { id: "string.uuid_empty" message: "value is empty, which is not a valid UUID" expression: "!rules.uuid || this != ''" } ]; // `tuuid` (trimmed UUID) specifies that the field value must be a valid UUID as // defined by [RFC 4122](https://tools.ietf.org/html/rfc4122#section-4.1.2) with all dashes // omitted. If the field value isn't a valid UUID without dashes, an error message // will be generated. // // ```proto // message MyString { // // value must be a valid trimmed UUID // string value = 1 [(buf.validate.field).string.tuuid = true]; // } // ``` bool tuuid = 33 [ (predefined).cel = { id: "string.tuuid" message: "value must be a valid trimmed UUID" expression: "!rules.tuuid || this == '' || this.matches('^[0-9a-fA-F]{32}$')" }, (predefined).cel = { id: "string.tuuid_empty" message: "value is empty, which is not a valid trimmed UUID" expression: "!rules.tuuid || this != ''" } ]; // `ip_with_prefixlen` specifies that the field value must be a valid IP (v4 or v6) // address with prefix length. If the field value isn't a valid IP with prefix // length, an error message will be generated. // // // ```proto // message MyString { // // value must be a valid IP with prefix length // string value = 1 [(buf.validate.field).string.ip_with_prefixlen = true]; // } // ``` bool ip_with_prefixlen = 26 [ (predefined).cel = { id: "string.ip_with_prefixlen" message: "value must be a valid IP prefix" expression: "!rules.ip_with_prefixlen || this == '' || this.isIpPrefix()" }, (predefined).cel = { id: "string.ip_with_prefixlen_empty" message: "value is empty, which is not a valid IP prefix" expression: "!rules.ip_with_prefixlen || this != ''" } ]; // `ipv4_with_prefixlen` specifies that the field value must be a valid // IPv4 address with prefix. // If the field value isn't a valid IPv4 address with prefix length, // an error message will be generated. // // ```proto // message MyString { // // value must be a valid IPv4 address with prefix length // string value = 1 [(buf.validate.field).string.ipv4_with_prefixlen = true]; // } // ``` bool ipv4_with_prefixlen = 27 [ (predefined).cel = { id: "string.ipv4_with_prefixlen" message: "value must be a valid IPv4 address with prefix length" expression: "!rules.ipv4_with_prefixlen || this == '' || this.isIpPrefix(4)" }, (predefined).cel = { id: "string.ipv4_with_prefixlen_empty" message: "value is empty, which is not a valid IPv4 address with prefix length" expression: "!rules.ipv4_with_prefixlen || this != ''" } ]; // `ipv6_with_prefixlen` specifies that the field value must be a valid // IPv6 address with prefix length. // If the field value is not a valid IPv6 address with prefix length, // an error message will be generated. // // ```proto // message MyString { // // value must be a valid IPv6 address prefix length // string value = 1 [(buf.validate.field).string.ipv6_with_prefixlen = true]; // } // ``` bool ipv6_with_prefixlen = 28 [ (predefined).cel = { id: "string.ipv6_with_prefixlen" message: "value must be a valid IPv6 address with prefix length" expression: "!rules.ipv6_with_prefixlen || this == '' || this.isIpPrefix(6)" }, (predefined).cel = { id: "string.ipv6_with_prefixlen_empty" message: "value is empty, which is not a valid IPv6 address with prefix length" expression: "!rules.ipv6_with_prefixlen || this != ''" } ]; // `ip_prefix` specifies that the field value must be a valid IP (v4 or v6) prefix. // If the field value isn't a valid IP prefix, an error message will be // generated. The prefix must have all zeros for the masked bits of the prefix (e.g., // `127.0.0.0/16`, not `127.0.0.1/16`). // // ```proto // message MyString { // // value must be a valid IP prefix // string value = 1 [(buf.validate.field).string.ip_prefix = true]; // } // ``` bool ip_prefix = 29 [ (predefined).cel = { id: "string.ip_prefix" message: "value must be a valid IP prefix" expression: "!rules.ip_prefix || this == '' || this.isIpPrefix(true)" }, (predefined).cel = { id: "string.ip_prefix_empty" message: "value is empty, which is not a valid IP prefix" expression: "!rules.ip_prefix || this != ''" } ]; // `ipv4_prefix` specifies that the field value must be a valid IPv4 // prefix. If the field value isn't a valid IPv4 prefix, an error message // will be generated. The prefix must have all zeros for the masked bits of // the prefix (e.g., `127.0.0.0/16`, not `127.0.0.1/16`). // // ```proto // message MyString { // // value must be a valid IPv4 prefix // string value = 1 [(buf.validate.field).string.ipv4_prefix = true]; // } // ``` bool ipv4_prefix = 30 [ (predefined).cel = { id: "string.ipv4_prefix" message: "value must be a valid IPv4 prefix" expression: "!rules.ipv4_prefix || this == '' || this.isIpPrefix(4, true)" }, (predefined).cel = { id: "string.ipv4_prefix_empty" message: "value is empty, which is not a valid IPv4 prefix" expression: "!rules.ipv4_prefix || this != ''" } ]; // `ipv6_prefix` specifies that the field value must be a valid IPv6 prefix. // If the field value is not a valid IPv6 prefix, an error message will be // generated. The prefix must have all zeros for the masked bits of the prefix // (e.g., `2001:db8::/48`, not `2001:db8::1/48`). // // ```proto // message MyString { // // value must be a valid IPv6 prefix // string value = 1 [(buf.validate.field).string.ipv6_prefix = true]; // } // ``` bool ipv6_prefix = 31 [ (predefined).cel = { id: "string.ipv6_prefix" message: "value must be a valid IPv6 prefix" expression: "!rules.ipv6_prefix || this == '' || this.isIpPrefix(6, true)" }, (predefined).cel = { id: "string.ipv6_prefix_empty" message: "value is empty, which is not a valid IPv6 prefix" expression: "!rules.ipv6_prefix || this != ''" } ]; // `host_and_port` specifies the field value must be a valid host and port // pair. The host must be a valid hostname or IP address while the port // must be in the range of 0-65535, inclusive. IPv6 addresses must be delimited // with square brackets (e.g., `[::1]:1234`). bool host_and_port = 32 [ (predefined).cel = { id: "string.host_and_port" message: "value must be a valid host (hostname or IP address) and port pair" expression: "!rules.host_and_port || this == '' || this.isHostAndPort(true)" }, (predefined).cel = { id: "string.host_and_port_empty" message: "value is empty, which is not a valid host and port pair" expression: "!rules.host_and_port || this != ''" } ]; // `well_known_regex` specifies a common well-known pattern // defined as a regex. If the field value doesn't match the well-known // regex, an error message will be generated. // // ```proto // message MyString { // // value must be a valid HTTP header value // string value = 1 [(buf.validate.field).string.well_known_regex = KNOWN_REGEX_HTTP_HEADER_VALUE]; // } // ``` // // #### KnownRegex // // `well_known_regex` contains some well-known patterns. // // | Name | Number | Description | // |-------------------------------|--------|-------------------------------------------| // | KNOWN_REGEX_UNSPECIFIED | 0 | | // | KNOWN_REGEX_HTTP_HEADER_NAME | 1 | HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2) | // | KNOWN_REGEX_HTTP_HEADER_VALUE | 2 | HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4) | KnownRegex well_known_regex = 24 [ (predefined).cel = { id: "string.well_known_regex.header_name" message: "value must be a valid HTTP header name" expression: "rules.well_known_regex != 1 || this == '' || this.matches(!has(rules.strict) || rules.strict ?" "'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :" "'^[^\\u0000\\u000A\\u000D]+$')" }, (predefined).cel = { id: "string.well_known_regex.header_name_empty" message: "value is empty, which is not a valid HTTP header name" expression: "rules.well_known_regex != 1 || this != ''" }, (predefined).cel = { id: "string.well_known_regex.header_value" message: "value must be a valid HTTP header value" expression: "rules.well_known_regex != 2 || this.matches(!has(rules.strict) || rules.strict ?" "'^[^\\u0000-\\u0008\\u000A-\\u001F\\u007F]*$' :" "'^[^\\u0000\\u000A\\u000D]*$')" } ]; } // This applies to regexes `HTTP_HEADER_NAME` and `HTTP_HEADER_VALUE` to // enable strict header validation. By default, this is true, and HTTP header // validations are [RFC-compliant](https://tools.ietf.org/html/rfc7230#section-3). Setting to false will enable looser // validations that only disallow `\r\n\0` characters, which can be used to // bypass header matching rules. // // ```proto // message MyString { // // The field `value` must have be a valid HTTP headers, but not enforced with strict rules. // string value = 1 [(buf.validate.field).string.strict = false]; // } // ``` optional bool strict = 25; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyString { // string value = 1 [ // (buf.validate.field).string.example = "hello", // (buf.validate.field).string.example = "world" // ]; // } // ``` repeated string example = 34 [(predefined).cel = { id: "string.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // WellKnownRegex contain some well-known patterns. enum KnownRegex { KNOWN_REGEX_UNSPECIFIED = 0; // HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2). KNOWN_REGEX_HTTP_HEADER_NAME = 1; // HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4). KNOWN_REGEX_HTTP_HEADER_VALUE = 2; } // BytesRules describe the constraints applied to `bytes` values. These rules // may also be applied to the `google.protobuf.BytesValue` Well-Known-Type. message BytesRules { // `const` requires the field value to exactly match the specified bytes // value. If the field value doesn't match, an error message is generated. // // ```proto // message MyBytes { // // value must be "\x01\x02\x03\x04" // bytes value = 1 [(buf.validate.field).bytes.const = "\x01\x02\x03\x04"]; // } // ``` optional bytes const = 1 [(predefined).cel = { id: "bytes.const" expression: "this != rules.const ? 'value must be %x'.format([rules.const]) : ''" }]; // `len` requires the field value to have the specified length in bytes. // If the field value doesn't match, an error message is generated. // // ```proto // message MyBytes { // // value length must be 4 bytes. // optional bytes value = 1 [(buf.validate.field).bytes.len = 4]; // } // ``` optional uint64 len = 13 [(predefined).cel = { id: "bytes.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s bytes'.format([rules.len]) : ''" }]; // `min_len` requires the field value to have at least the specified minimum // length in bytes. // If the field value doesn't meet the requirement, an error message is generated. // // ```proto // message MyBytes { // // value length must be at least 2 bytes. // optional bytes value = 1 [(buf.validate.field).bytes.min_len = 2]; // } // ``` optional uint64 min_len = 2 [(predefined).cel = { id: "bytes.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s bytes'.format([rules.min_len]) : ''" }]; // `max_len` requires the field value to have at most the specified maximum // length in bytes. // If the field value exceeds the requirement, an error message is generated. // // ```proto // message MyBytes { // // value must be at most 6 bytes. // optional bytes value = 1 [(buf.validate.field).bytes.max_len = 6]; // } // ``` optional uint64 max_len = 3 [(predefined).cel = { id: "bytes.max_len" expression: "uint(this.size()) > rules.max_len ? 'value must be at most %s bytes'.format([rules.max_len]) : ''" }]; // `pattern` requires the field value to match the specified regular // expression ([RE2 syntax](https://github.com/google/re2/wiki/Syntax)). // The value of the field must be valid UTF-8 or validation will fail with a // runtime error. // If the field value doesn't match the pattern, an error message is generated. // // ```proto // message MyBytes { // // value must match regex pattern "^[a-zA-Z0-9]+$". // optional bytes value = 1 [(buf.validate.field).bytes.pattern = "^[a-zA-Z0-9]+$"]; // } // ``` optional string pattern = 4 [(predefined).cel = { id: "bytes.pattern" expression: "!string(this).matches(rules.pattern) ? 'value must match regex pattern `%s`'.format([rules.pattern]) : ''" }]; // `prefix` requires the field value to have the specified bytes at the // beginning of the string. // If the field value doesn't meet the requirement, an error message is generated. // // ```proto // message MyBytes { // // value does not have prefix \x01\x02 // optional bytes value = 1 [(buf.validate.field).bytes.prefix = "\x01\x02"]; // } // ``` optional bytes prefix = 5 [(predefined).cel = { id: "bytes.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix %x'.format([rules.prefix]) : ''" }]; // `suffix` requires the field value to have the specified bytes at the end // of the string. // If the field value doesn't meet the requirement, an error message is generated. // // ```proto // message MyBytes { // // value does not have suffix \x03\x04 // optional bytes value = 1 [(buf.validate.field).bytes.suffix = "\x03\x04"]; // } // ``` optional bytes suffix = 6 [(predefined).cel = { id: "bytes.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix %x'.format([rules.suffix]) : ''" }]; // `contains` requires the field value to have the specified bytes anywhere in // the string. // If the field value doesn't meet the requirement, an error message is generated. // // ```protobuf // message MyBytes { // // value does not contain \x02\x03 // optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"]; // } // ``` optional bytes contains = 7 [(predefined).cel = { id: "bytes.contains" expression: "!this.contains(rules.contains) ? 'value does not contain %x'.format([rules.contains]) : ''" }]; // `in` requires the field value to be equal to one of the specified // values. If the field value doesn't match any of the specified values, an // error message is generated. // // ```protobuf // message MyBytes { // // value must in ["\x01\x02", "\x02\x03", "\x03\x04"] // optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` repeated bytes in = 8 [(predefined).cel = { id: "bytes.in" expression: "dyn(rules)['in'].size() > 0 && !(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to be not equal to any of the specified // values. // If the field value matches any of the specified values, an error message is // generated. // // ```proto // message MyBytes { // // value must not in ["\x01\x02", "\x02\x03", "\x03\x04"] // optional bytes value = 1 [(buf.validate.field).bytes.not_in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` repeated bytes not_in = 9 [(predefined).cel = { id: "bytes.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // WellKnown rules provide advanced constraints against common byte // patterns oneof well_known { // `ip` ensures that the field `value` is a valid IP address (v4 or v6) in byte format. // If the field value doesn't meet this constraint, an error message is generated. // // ```proto // message MyBytes { // // value must be a valid IP address // optional bytes value = 1 [(buf.validate.field).bytes.ip = true]; // } // ``` bool ip = 10 [ (predefined).cel = { id: "bytes.ip" message: "value must be a valid IP address" expression: "!rules.ip || this.size() == 0 || this.size() == 4 || this.size() == 16" }, (predefined).cel = { id: "bytes.ip_empty" message: "value is empty, which is not a valid IP address" expression: "!rules.ip || this.size() != 0" } ]; // `ipv4` ensures that the field `value` is a valid IPv4 address in byte format. // If the field value doesn't meet this constraint, an error message is generated. // // ```proto // message MyBytes { // // value must be a valid IPv4 address // optional bytes value = 1 [(buf.validate.field).bytes.ipv4 = true]; // } // ``` bool ipv4 = 11 [ (predefined).cel = { id: "bytes.ipv4" message: "value must be a valid IPv4 address" expression: "!rules.ipv4 || this.size() == 0 || this.size() == 4" }, (predefined).cel = { id: "bytes.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" expression: "!rules.ipv4 || this.size() != 0" } ]; // `ipv6` ensures that the field `value` is a valid IPv6 address in byte format. // If the field value doesn't meet this constraint, an error message is generated. // ```proto // message MyBytes { // // value must be a valid IPv6 address // optional bytes value = 1 [(buf.validate.field).bytes.ipv6 = true]; // } // ``` bool ipv6 = 12 [ (predefined).cel = { id: "bytes.ipv6" message: "value must be a valid IPv6 address" expression: "!rules.ipv6 || this.size() == 0 || this.size() == 16" }, (predefined).cel = { id: "bytes.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" expression: "!rules.ipv6 || this.size() != 0" } ]; } // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyBytes { // bytes value = 1 [ // (buf.validate.field).bytes.example = "\x01\x02", // (buf.validate.field).bytes.example = "\x02\x03" // ]; // } // ``` repeated bytes example = 14 [(predefined).cel = { id: "bytes.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // EnumRules describe the constraints applied to `enum` values. message EnumRules { // `const` requires the field value to exactly match the specified enum value. // If the field value doesn't match, an error message is generated. // // ```proto // enum MyEnum { // MY_ENUM_UNSPECIFIED = 0; // MY_ENUM_VALUE1 = 1; // MY_ENUM_VALUE2 = 2; // } // // message MyMessage { // // The field `value` must be exactly MY_ENUM_VALUE1. // MyEnum value = 1 [(buf.validate.field).enum.const = 1]; // } // ``` optional int32 const = 1 [(predefined).cel = { id: "enum.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; // `defined_only` requires the field value to be one of the defined values for // this enum, failing on any undefined value. // // ```proto // enum MyEnum { // MY_ENUM_UNSPECIFIED = 0; // MY_ENUM_VALUE1 = 1; // MY_ENUM_VALUE2 = 2; // } // // message MyMessage { // // The field `value` must be a defined value of MyEnum. // MyEnum value = 1 [(buf.validate.field).enum.defined_only = true]; // } // ``` optional bool defined_only = 2; // `in` requires the field value to be equal to one of the //specified enum values. If the field value doesn't match any of the //specified values, an error message is generated. // // ```proto // enum MyEnum { // MY_ENUM_UNSPECIFIED = 0; // MY_ENUM_VALUE1 = 1; // MY_ENUM_VALUE2 = 2; // } // // message MyMessage { // // The field `value` must be equal to one of the specified values. // MyEnum value = 1 [(buf.validate.field).enum = { in: [1, 2]}]; // } // ``` repeated int32 in = 3 [(predefined).cel = { id: "enum.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` requires the field value to be not equal to any of the //specified enum values. If the field value matches one of the specified // values, an error message is generated. // // ```proto // enum MyEnum { // MY_ENUM_UNSPECIFIED = 0; // MY_ENUM_VALUE1 = 1; // MY_ENUM_VALUE2 = 2; // } // // message MyMessage { // // The field `value` must not be equal to any of the specified values. // MyEnum value = 1 [(buf.validate.field).enum = { not_in: [1, 2]}]; // } // ``` repeated int32 not_in = 4 [(predefined).cel = { id: "enum.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // enum MyEnum { // MY_ENUM_UNSPECIFIED = 0; // MY_ENUM_VALUE1 = 1; // MY_ENUM_VALUE2 = 2; // } // // message MyMessage { // (buf.validate.field).enum.example = 1, // (buf.validate.field).enum.example = 2 // } // ``` repeated int32 example = 5 [(predefined).cel = { id: "enum.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // RepeatedRules describe the constraints applied to `repeated` values. message RepeatedRules { // `min_items` requires that this field must contain at least the specified // minimum number of items. // // Note that `min_items = 1` is equivalent to setting a field as `required`. // // ```proto // message MyRepeated { // // value must contain at least 2 items // repeated string value = 1 [(buf.validate.field).repeated.min_items = 2]; // } // ``` optional uint64 min_items = 1 [(predefined).cel = { id: "repeated.min_items" expression: "uint(this.size()) < rules.min_items ? 'value must contain at least %d item(s)'.format([rules.min_items]) : ''" }]; // `max_items` denotes that this field must not exceed a // certain number of items as the upper limit. If the field contains more // items than specified, an error message will be generated, requiring the // field to maintain no more than the specified number of items. // // ```proto // message MyRepeated { // // value must contain no more than 3 item(s) // repeated string value = 1 [(buf.validate.field).repeated.max_items = 3]; // } // ``` optional uint64 max_items = 2 [(predefined).cel = { id: "repeated.max_items" expression: "uint(this.size()) > rules.max_items ? 'value must contain no more than %s item(s)'.format([rules.max_items]) : ''" }]; // `unique` indicates that all elements in this field must // be unique. This constraint is strictly applicable to scalar and enum // types, with message types not being supported. // // ```proto // message MyRepeated { // // repeated value must contain unique items // repeated string value = 1 [(buf.validate.field).repeated.unique = true]; // } // ``` optional bool unique = 3 [(predefined).cel = { id: "repeated.unique" message: "repeated value must contain unique items" expression: "!rules.unique || this.unique()" }]; // `items` details the constraints to be applied to each item // in the field. Even for repeated message fields, validation is executed // against each item unless skip is explicitly specified. // // ```proto // message MyRepeated { // // The items in the field `value` must follow the specified constraints. // repeated string value = 1 [(buf.validate.field).repeated.items = { // string: { // min_len: 3 // max_len: 10 // } // }]; // } // ``` optional FieldConstraints items = 4; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // MapRules describe the constraints applied to `map` values. message MapRules { //Specifies the minimum number of key-value pairs allowed. If the field has // fewer key-value pairs than specified, an error message is generated. // // ```proto // message MyMap { // // The field `value` must have at least 2 key-value pairs. // map value = 1 [(buf.validate.field).map.min_pairs = 2]; // } // ``` optional uint64 min_pairs = 1 [(predefined).cel = { id: "map.min_pairs" expression: "uint(this.size()) < rules.min_pairs ? 'map must be at least %d entries'.format([rules.min_pairs]) : ''" }]; //Specifies the maximum number of key-value pairs allowed. If the field has // more key-value pairs than specified, an error message is generated. // // ```proto // message MyMap { // // The field `value` must have at most 3 key-value pairs. // map value = 1 [(buf.validate.field).map.max_pairs = 3]; // } // ``` optional uint64 max_pairs = 2 [(predefined).cel = { id: "map.max_pairs" expression: "uint(this.size()) > rules.max_pairs ? 'map must be at most %d entries'.format([rules.max_pairs]) : ''" }]; //Specifies the constraints to be applied to each key in the field. // // ```proto // message MyMap { // // The keys in the field `value` must follow the specified constraints. // map value = 1 [(buf.validate.field).map.keys = { // string: { // min_len: 3 // max_len: 10 // } // }]; // } // ``` optional FieldConstraints keys = 4; //Specifies the constraints to be applied to the value of each key in the // field. Message values will still have their validations evaluated unless //skip is specified here. // // ```proto // message MyMap { // // The values in the field `value` must follow the specified constraints. // map value = 1 [(buf.validate.field).map.values = { // string: { // min_len: 5 // max_len: 20 // } // }]; // } // ``` optional FieldConstraints values = 5; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // AnyRules describe constraints applied exclusively to the `google.protobuf.Any` well-known type. message AnyRules { // `in` requires the field's `type_url` to be equal to one of the //specified values. If it doesn't match any of the specified values, an error // message is generated. // // ```proto // message MyAny { // // The `value` field must have a `type_url` equal to one of the specified values. // google.protobuf.Any value = 1 [(buf.validate.field).any.in = ["type.googleapis.com/MyType1", "type.googleapis.com/MyType2"]]; // } // ``` repeated string in = 2; // requires the field's type_url to be not equal to any of the specified values. If it matches any of the specified values, an error message is generated. // // ```proto // message MyAny { // // The field `value` must not have a `type_url` equal to any of the specified values. // google.protobuf.Any value = 1 [(buf.validate.field).any.not_in = ["type.googleapis.com/ForbiddenType1", "type.googleapis.com/ForbiddenType2"]]; // } // ``` repeated string not_in = 3; } // DurationRules describe the constraints applied exclusively to the `google.protobuf.Duration` well-known type. message DurationRules { // `const` dictates that the field must match the specified value of the `google.protobuf.Duration` type exactly. // If the field's value deviates from the specified value, an error message // will be generated. // // ```proto // message MyDuration { // // value must equal 5s // google.protobuf.Duration value = 1 [(buf.validate.field).duration.const = "5s"]; // } // ``` optional google.protobuf.Duration const = 2 [(predefined).cel = { id: "duration.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // `lt` stipulates that the field must be less than the specified value of the `google.protobuf.Duration` type, // exclusive. If the field's value is greater than or equal to the specified // value, an error message will be generated. // // ```proto // message MyDuration { // // value must be less than 5s // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = "5s"]; // } // ``` google.protobuf.Duration lt = 3 [(predefined).cel = { id: "duration.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // `lte` indicates that the field must be less than or equal to the specified // value of the `google.protobuf.Duration` type, inclusive. If the field's value is greater than the specified value, // an error message will be generated. // // ```proto // message MyDuration { // // value must be less than or equal to 10s // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lte = "10s"]; // } // ``` google.protobuf.Duration lte = 4 [(predefined).cel = { id: "duration.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; } oneof greater_than { // `gt` requires the duration field value to be greater than the specified // value (exclusive). If the value of `gt` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyDuration { // // duration must be greater than 5s [duration.gt] // google.protobuf.Duration value = 1 [(buf.validate.field).duration.gt = { seconds: 5 }]; // // // duration must be greater than 5s and less than 10s [duration.gt_lt] // google.protobuf.Duration another_value = 2 [(buf.validate.field).duration = { gt: { seconds: 5 }, lt: { seconds: 10 } }]; // // // duration must be greater than 10s or less than 5s [duration.gt_lt_exclusive] // google.protobuf.Duration other_value = 3 [(buf.validate.field).duration = { gt: { seconds: 10 }, lt: { seconds: 5 } }]; // } // ``` google.protobuf.Duration gt = 5 [ (predefined).cel = { id: "duration.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "duration.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "duration.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "duration.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "duration.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the duration field value to be greater than or equal to the // specified value (exclusive). If the value of `gte` is larger than a // specified `lt` or `lte`, the range is reversed, and the field value must // be outside the specified range. If the field value doesn't meet the // required conditions, an error message is generated. // // ```proto // message MyDuration { // // duration must be greater than or equal to 5s [duration.gte] // google.protobuf.Duration value = 1 [(buf.validate.field).duration.gte = { seconds: 5 }]; // // // duration must be greater than or equal to 5s and less than 10s [duration.gte_lt] // google.protobuf.Duration another_value = 2 [(buf.validate.field).duration = { gte: { seconds: 5 }, lt: { seconds: 10 } }]; // // // duration must be greater than or equal to 10s or less than 5s [duration.gte_lt_exclusive] // google.protobuf.Duration other_value = 3 [(buf.validate.field).duration = { gte: { seconds: 10 }, lt: { seconds: 5 } }]; // } // ``` google.protobuf.Duration gte = 6 [ (predefined).cel = { id: "duration.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "duration.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "duration.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "duration.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "duration.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; } // `in` asserts that the field must be equal to one of the specified values of the `google.protobuf.Duration` type. // If the field's value doesn't correspond to any of the specified values, // an error message will be generated. // // ```proto // message MyDuration { // // value must be in list [1s, 2s, 3s] // google.protobuf.Duration value = 1 [(buf.validate.field).duration.in = ["1s", "2s", "3s"]]; // } // ``` repeated google.protobuf.Duration in = 7 [(predefined).cel = { id: "duration.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; // `not_in` denotes that the field must not be equal to // any of the specified values of the `google.protobuf.Duration` type. // If the field's value matches any of these values, an error message will be // generated. // // ```proto // message MyDuration { // // value must not be in list [1s, 2s, 3s] // google.protobuf.Duration value = 1 [(buf.validate.field).duration.not_in = ["1s", "2s", "3s"]]; // } // ``` repeated google.protobuf.Duration not_in = 8 [(predefined).cel = { id: "duration.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyDuration { // google.protobuf.Duration value = 1 [ // (buf.validate.field).duration.example = { seconds: 1 }, // (buf.validate.field).duration.example = { seconds: 2 }, // ]; // } // ``` repeated google.protobuf.Duration example = 9 [(predefined).cel = { id: "duration.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // TimestampRules describe the constraints applied exclusively to the `google.protobuf.Timestamp` well-known type. message TimestampRules { // `const` dictates that this field, of the `google.protobuf.Timestamp` type, must exactly match the specified value. If the field value doesn't correspond to the specified timestamp, an error message will be generated. // // ```proto // message MyTimestamp { // // value must equal 2023-05-03T10:00:00Z // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.const = {seconds: 1727998800}]; // } // ``` optional google.protobuf.Timestamp const = 2 [(predefined).cel = { id: "timestamp.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; oneof less_than { // requires the duration field value to be less than the specified value (field < value). If the field value doesn't meet the required conditions, an error message is generated. // // ```proto // message MyDuration { // // duration must be less than 'P3D' [duration.lt] // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = { seconds: 259200 }]; // } // ``` google.protobuf.Timestamp lt = 3 [(predefined).cel = { id: "timestamp.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" "? 'value must be less than %s'.format([rules.lt]) : ''" }]; // requires the timestamp field value to be less than or equal to the specified value (field <= value). If the field value doesn't meet the required conditions, an error message is generated. // // ```proto // message MyTimestamp { // // timestamp must be less than or equal to '2023-05-14T00:00:00Z' [timestamp.lte] // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.lte = { seconds: 1678867200 }]; // } // ``` google.protobuf.Timestamp lte = 4 [(predefined).cel = { id: "timestamp.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" }]; // `lt_now` specifies that this field, of the `google.protobuf.Timestamp` type, must be less than the current time. `lt_now` can only be used with the `within` rule. // // ```proto // message MyTimestamp { // // value must be less than now // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.lt_now = true]; // } // ``` bool lt_now = 7 [(predefined).cel = { id: "timestamp.lt_now" expression: "(rules.lt_now && this > now) ? 'value must be less than now' : ''" }]; } oneof greater_than { // `gt` requires the timestamp field value to be greater than the specified // value (exclusive). If the value of `gt` is larger than a specified `lt` // or `lte`, the range is reversed, and the field value must be outside the // specified range. If the field value doesn't meet the required conditions, // an error message is generated. // // ```proto // message MyTimestamp { // // timestamp must be greater than '2023-01-01T00:00:00Z' [timestamp.gt] // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.gt = { seconds: 1672444800 }]; // // // timestamp must be greater than '2023-01-01T00:00:00Z' and less than '2023-01-02T00:00:00Z' [timestamp.gt_lt] // google.protobuf.Timestamp another_value = 2 [(buf.validate.field).timestamp = { gt: { seconds: 1672444800 }, lt: { seconds: 1672531200 } }]; // // // timestamp must be greater than '2023-01-02T00:00:00Z' or less than '2023-01-01T00:00:00Z' [timestamp.gt_lt_exclusive] // google.protobuf.Timestamp other_value = 3 [(buf.validate.field).timestamp = { gt: { seconds: 1672531200 }, lt: { seconds: 1672444800 } }]; // } // ``` google.protobuf.Timestamp gt = 5 [ (predefined).cel = { id: "timestamp.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, (predefined).cel = { id: "timestamp.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "timestamp.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, (predefined).cel = { id: "timestamp.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, (predefined).cel = { id: "timestamp.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" } ]; // `gte` requires the timestamp field value to be greater than or equal to the // specified value (exclusive). If the value of `gte` is larger than a // specified `lt` or `lte`, the range is reversed, and the field value // must be outside the specified range. If the field value doesn't meet // the required conditions, an error message is generated. // // ```proto // message MyTimestamp { // // timestamp must be greater than or equal to '2023-01-01T00:00:00Z' [timestamp.gte] // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.gte = { seconds: 1672444800 }]; // // // timestamp must be greater than or equal to '2023-01-01T00:00:00Z' and less than '2023-01-02T00:00:00Z' [timestamp.gte_lt] // google.protobuf.Timestamp another_value = 2 [(buf.validate.field).timestamp = { gte: { seconds: 1672444800 }, lt: { seconds: 1672531200 } }]; // // // timestamp must be greater than or equal to '2023-01-02T00:00:00Z' or less than '2023-01-01T00:00:00Z' [timestamp.gte_lt_exclusive] // google.protobuf.Timestamp other_value = 3 [(buf.validate.field).timestamp = { gte: { seconds: 1672531200 }, lt: { seconds: 1672444800 } }]; // } // ``` google.protobuf.Timestamp gte = 6 [ (predefined).cel = { id: "timestamp.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, (predefined).cel = { id: "timestamp.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "timestamp.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, (predefined).cel = { id: "timestamp.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, (predefined).cel = { id: "timestamp.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" } ]; // `gt_now` specifies that this field, of the `google.protobuf.Timestamp` type, must be greater than the current time. `gt_now` can only be used with the `within` rule. // // ```proto // message MyTimestamp { // // value must be greater than now // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.gt_now = true]; // } // ``` bool gt_now = 8 [(predefined).cel = { id: "timestamp.gt_now" expression: "(rules.gt_now && this < now) ? 'value must be greater than now' : ''" }]; } // `within` specifies that this field, of the `google.protobuf.Timestamp` type, must be within the specified duration of the current time. If the field value isn't within the duration, an error message is generated. // // ```proto // message MyTimestamp { // // value must be within 1 hour of now // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.within = {seconds: 3600}]; // } // ``` optional google.protobuf.Duration within = 9 [(predefined).cel = { id: "timestamp.within" expression: "this < now-rules.within || this > now+rules.within ? 'value must be within %s of now'.format([rules.within]) : ''" }]; // `example` specifies values that the field may have. These values SHOULD // conform to other constraints. `example` values will not impact validation // but may be used as helpful guidance on how to populate the given field. // // ```proto // message MyTimestamp { // google.protobuf.Timestamp value = 1 [ // (buf.validate.field).timestamp.example = { seconds: 1672444800 }, // (buf.validate.field).timestamp.example = { seconds: 1672531200 }, // ]; // } // ``` repeated google.protobuf.Timestamp example = 10 [(predefined).cel = { id: "timestamp.example" expression: "true" }]; // Extension fields in this range that have the (buf.validate.predefined) // option set will be treated as predefined field constraints that can then be // set on the field options of other fields to apply field constraints. // Extension numbers 1000 to 99999 are reserved for extension numbers that are // defined in the [Protobuf Global Extension Registry][1]. Extension numbers // above this range are reserved for extension numbers that are not explicitly // assigned. For rules defined in publicly-consumed schemas, use of extensions // above 99999 is discouraged due to the risk of conflicts. // // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md extensions 1000 to max; } // `Violations` is a collection of `Violation` messages. This message type is returned by // protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. // Each individual violation is represented by a `Violation` message. message Violations { // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. repeated Violation violations = 1; } // `Violation` represents a single instance where a validation rule, expressed // as a `Constraint`, was not met. It provides information about the field that // caused the violation, the specific constraint that wasn't fulfilled, and a // human-readable error message. // // ```json // { // "fieldPath": "bar", // "constraintId": "foo.bar", // "message": "bar must be greater than 0" // } // ``` message Violation { // `field` is a machine-readable path to the field that failed validation. // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. // // For example, consider the following message: // // ```proto // message Message { // bool a = 1 [(buf.validate.field).required = true]; // } // ``` // // It could produce the following violation: // // ```textproto // violation { // field { element { field_number: 1, field_name: "a", field_type: 8 } } // ... // } // ``` optional FieldPath field = 5; // `rule` is a machine-readable path that points to the specific constraint rule that failed validation. // This will be a nested field starting from the FieldConstraints of the field that failed validation. // For custom constraints, this will provide the path of the constraint, e.g. `cel[0]`. // // For example, consider the following message: // // ```proto // message Message { // bool a = 1 [(buf.validate.field).required = true]; // bool b = 2 [(buf.validate.field).cel = { // id: "custom_constraint", // expression: "!this ? 'b must be true': ''" // }] // } // ``` // // It could produce the following violations: // // ```textproto // violation { // rule { element { field_number: 25, field_name: "required", field_type: 8 } } // ... // } // violation { // rule { element { field_number: 23, field_name: "cel", field_type: 11, index: 0 } } // ... // } // ``` optional FieldPath rule = 6; // `field_path` is a human-readable identifier that points to the specific field that failed the validation. // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. // // Deprecated: use the `field` instead. optional string field_path = 1 [deprecated = true]; // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. optional string constraint_id = 2; // `message` is a human-readable error message that describes the nature of the violation. // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. optional string message = 3; // `for_key` indicates whether the violation was caused by a map key, rather than a value. optional bool for_key = 4; } // `FieldPath` provides a path to a nested protobuf field. // // This message provides enough information to render a dotted field path even without protobuf descriptors. // It also provides enough information to resolve a nested field through unknown wire data. message FieldPath { // `elements` contains each element of the path, starting from the root and recursing downward. repeated FieldPathElement elements = 1; } // `FieldPathElement` provides enough information to nest through a single protobuf field. // // If the selected field is a map or repeated field, the `subscript` value selects a specific element from it. // A path that refers to a value nested under a map key or repeated field index will have a `subscript` value. // The `field_type` field allows unambiguous resolution of a field even if descriptors are not available. message FieldPathElement { // `field_number` is the field number this path element refers to. optional int32 field_number = 1; // `field_name` contains the field name this path element refers to. // This can be used to display a human-readable path even if the field number is unknown. optional string field_name = 2; // `field_type` specifies the type of this field. When using reflection, this value is not needed. // // This value is provided to make it possible to traverse unknown fields through wire data. // When traversing wire data, be mindful of both packed[1] and delimited[2] encoding schemes. // // [1]: https://protobuf.dev/programming-guides/encoding/#packed // [2]: https://protobuf.dev/programming-guides/encoding/#groups // // N.B.: Although groups are deprecated, the corresponding delimited encoding scheme is not, and // can be explicitly used in Protocol Buffers 2023 Edition. optional google.protobuf.FieldDescriptorProto.Type field_type = 3; // `key_type` specifies the map key type of this field. This value is useful when traversing // unknown fields through wire data: specifically, it allows handling the differences between // different integer encodings. optional google.protobuf.FieldDescriptorProto.Type key_type = 4; // `value_type` specifies map value type of this field. This is useful if you want to display a // value inside unknown fields through wire data. optional google.protobuf.FieldDescriptorProto.Type value_type = 5; // `subscript` contains a repeated index or map key, if this path element nests into a repeated or map field. oneof subscript { // `index` specifies a 0-based index into a repeated field. uint64 index = 6; // `bool_key` specifies a map key of type bool. bool bool_key = 7; // `int_key` specifies a map key of type int32, int64, sint32, sint64, sfixed32 or sfixed64. int64 int_key = 8; // `uint_key` specifies a map key of type uint32, uint64, fixed32 or fixed64. uint64 uint_key = 9; // `string_key` specifies a map key of type string. string string_key = 10; } } ================================================ FILE: third_party/buf.yaml ================================================ version: v1 name: buf.build/kratos/apis breaking: use: - FILE lint: use: - DEFAULT build: excludes: - google - validate ================================================ FILE: third_party/errors/errors.proto ================================================ 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"; extend google.protobuf.EnumOptions { int32 default_code = 1108; } extend google.protobuf.EnumValueOptions { int32 code = 1109; } ================================================ FILE: third_party/google/api/annotations.proto ================================================ // Copyright (c) 2015, Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; import "google/api/http.proto"; import "google/protobuf/descriptor.proto"; option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; option java_multiple_files = true; option java_outer_classname = "AnnotationsProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; extend google.protobuf.MethodOptions { // See `HttpRule`. HttpRule http = 72295728; } ================================================ FILE: third_party/google/api/client.proto ================================================ // Copyright 2019 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; import "google/protobuf/descriptor.proto"; option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; option java_multiple_files = true; option java_outer_classname = "ClientProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; extend google.protobuf.ServiceOptions { // The hostname for this service. // This should be specified with no prefix or protocol. // // Example: // // service Foo { // option (google.api.default_host) = "foo.googleapi.com"; // ... // } string default_host = 1049; // OAuth scopes needed for the client. // // Example: // // service Foo { // option (google.api.oauth_scopes) = \ // "https://www.googleapis.com/auth/cloud-platform"; // ... // } // // If there is more than one scope, use a comma-separated string: // // Example: // // service Foo { // option (google.api.oauth_scopes) = \ // "https://www.googleapis.com/auth/cloud-platform," // "https://www.googleapis.com/auth/monitoring"; // ... // } string oauth_scopes = 1050; } extend google.protobuf.MethodOptions { // A definition of a client library method signature. // // In client libraries, each proto RPC corresponds to one or more methods // which the end user is able to call, and calls the underlying RPC. // Normally, this method receives a single argument (a struct or instance // corresponding to the RPC request object). Defining this field will // add one or more overloads providing flattened or simpler method signatures // in some languages. // // The fields on the method signature are provided as a comma-separated // string. // // For example, the proto RPC and annotation: // // rpc CreateSubscription(CreateSubscriptionRequest) // returns (Subscription) { // option (google.api.method_signature) = "name,topic"; // } // // Would add the following Java overload (in addition to the method accepting // the request object): // // public final Subscription createSubscription(String name, String topic) // // The following backwards-compatibility guidelines apply: // // * Adding this annotation to an unannotated method is backwards // compatible. // * Adding this annotation to a method which already has existing // method signature annotations is backwards compatible if and only if // the new method signature annotation is last in the sequence. // * Modifying or removing an existing method signature annotation is // a breaking change. // * Re-ordering existing method signature annotations is a breaking // change. repeated string method_signature = 1051; } ================================================ FILE: third_party/google/api/field_behavior.proto ================================================ // Copyright 2019 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; import "google/protobuf/descriptor.proto"; option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; option java_multiple_files = true; option java_outer_classname = "FieldBehaviorProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; // An indicator of the behavior of a given field (for example, that a field // is required in requests, or given as output but ignored as input). // This **does not** change the behavior in protocol buffers itself; it only // denotes the behavior and may affect how API tooling handles the field. // // Note: This enum **may** receive new values in the future. enum FieldBehavior { // Conventional default for enums. Do not use this. FIELD_BEHAVIOR_UNSPECIFIED = 0; // Specifically denotes a field as optional. // While all fields in protocol buffers are optional, this may be specified // for emphasis if appropriate. OPTIONAL = 1; // Denotes a field as required. // This indicates that the field **must** be provided as part of the request, // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). REQUIRED = 2; // Denotes a field as output only. // This indicates that the field is provided in responses, but including the // field in a request does nothing (the server *must* ignore it and // *must not* throw an error as a result of the field's presence). OUTPUT_ONLY = 3; // Denotes a field as input only. // This indicates that the field is provided in requests, and the // corresponding field is not included in output. INPUT_ONLY = 4; // Denotes a field as immutable. // This indicates that the field may be set once in a request to create a // resource, but may not be changed thereafter. IMMUTABLE = 5; } extend google.protobuf.FieldOptions { // A designation of a specific field behavior (required, output only, etc.) // in protobuf messages. // // Examples: // // string name = 1 [(google.api.field_behavior) = REQUIRED]; // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; // google.protobuf.Duration ttl = 1 // [(google.api.field_behavior) = INPUT_ONLY]; // google.protobuf.Timestamp expire_time = 1 // [(google.api.field_behavior) = OUTPUT_ONLY, // (google.api.field_behavior) = IMMUTABLE]; repeated FieldBehavior field_behavior = 1052; } ================================================ FILE: third_party/google/api/http.proto ================================================ // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; option cc_enable_arenas = true; option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; option java_multiple_files = true; option java_outer_classname = "HttpProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; // Defines the HTTP configuration for an API service. It contains a list of // [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method // to one or more HTTP REST API methods. message Http { // A list of HTTP configuration rules that apply to individual API methods. // // **NOTE:** All service configuration rules follow "last one wins" order. repeated HttpRule rules = 1; // When set to true, URL path parameters will be fully URI-decoded except in // cases of single segment matches in reserved expansion, where "%2F" will be // left encoded. // // The default behavior is to not decode RFC 6570 reserved characters in multi // segment matches. bool fully_decode_reserved_expansion = 2; } // # gRPC Transcoding // // gRPC Transcoding is a feature for mapping between a gRPC method and one or // more HTTP REST endpoints. It allows developers to build a single API service // that supports both gRPC APIs and REST APIs. Many systems, including [Google // APIs](https://github.com/googleapis/googleapis), // [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC // Gateway](https://github.com/grpc-ecosystem/grpc-gateway), // and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature // and use it for large scale production services. // // `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies // how different portions of the gRPC request message are mapped to the URL // path, URL query parameters, and HTTP request body. It also controls how the // gRPC response message is mapped to the HTTP response body. `HttpRule` is // typically specified as an `google.api.http` annotation on the gRPC method. // // Each mapping specifies a URL path template and an HTTP method. The path // template may refer to one or more fields in the gRPC request message, as long // as each field is a non-repeated field with a primitive (non-message) type. // The path template controls how fields of the request message are mapped to // the URL path. // // Example: // // service Messaging { // rpc GetMessage(GetMessageRequest) returns (Message) { // option (google.api.http) = { // get: "/v1/{name=messages/*}" // }; // } // } // message GetMessageRequest { // string name = 1; // Mapped to URL path. // } // message Message { // string text = 1; // The resource content. // } // // This enables an HTTP REST to gRPC mapping as below: // // HTTP | gRPC // -----|----- // `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` // // Any fields in the request message which are not bound by the path template // automatically become HTTP query parameters if there is no HTTP request body. // For example: // // service Messaging { // rpc GetMessage(GetMessageRequest) returns (Message) { // option (google.api.http) = { // get:"/v1/messages/{message_id}" // }; // } // } // message GetMessageRequest { // message SubMessage { // string subfield = 1; // } // string message_id = 1; // Mapped to URL path. // int64 revision = 2; // Mapped to URL query parameter `revision`. // SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. // } // // This enables a HTTP JSON to RPC mapping as below: // // HTTP | gRPC // -----|----- // `GET /v1/messages/123456?revision=2&sub.subfield=foo` | // `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: // "foo"))` // // Note that fields which are mapped to URL query parameters must have a // primitive type or a repeated primitive type or a non-repeated message type. // In the case of a repeated type, the parameter can be repeated in the URL // as `...?param=A¶m=B`. In the case of a message type, each field of the // message is mapped to a separate parameter, such as // `...?foo.a=A&foo.b=B&foo.c=C`. // // For HTTP methods that allow a request body, the `body` field // specifies the mapping. Consider a REST update method on the // message resource collection: // // service Messaging { // rpc UpdateMessage(UpdateMessageRequest) returns (Message) { // option (google.api.http) = { // patch: "/v1/messages/{message_id}" // body: "message" // }; // } // } // message UpdateMessageRequest { // string message_id = 1; // mapped to the URL // Message message = 2; // mapped to the body // } // // The following HTTP JSON to RPC mapping is enabled, where the // representation of the JSON in the request body is determined by // protos JSON encoding: // // HTTP | gRPC // -----|----- // `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: // "123456" message { text: "Hi!" })` // // The special name `*` can be used in the body mapping to define that // every field not bound by the path template should be mapped to the // request body. This enables the following alternative definition of // the update method: // // service Messaging { // rpc UpdateMessage(Message) returns (Message) { // option (google.api.http) = { // patch: "/v1/messages/{message_id}" // body: "*" // }; // } // } // message Message { // string message_id = 1; // string text = 2; // } // // // The following HTTP JSON to RPC mapping is enabled: // // HTTP | gRPC // -----|----- // `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: // "123456" text: "Hi!")` // // Note that when using `*` in the body mapping, it is not possible to // have HTTP parameters, as all fields not bound by the path end in // the body. This makes this option more rarely used in practice when // defining REST APIs. The common usage of `*` is in custom methods // which don't use the URL at all for transferring data. // // It is possible to define multiple HTTP methods for one RPC by using // the `additional_bindings` option. Example: // // service Messaging { // rpc GetMessage(GetMessageRequest) returns (Message) { // option (google.api.http) = { // get: "/v1/messages/{message_id}" // additional_bindings { // get: "/v1/users/{user_id}/messages/{message_id}" // } // }; // } // } // message GetMessageRequest { // string message_id = 1; // string user_id = 2; // } // // This enables the following two alternative HTTP JSON to RPC mappings: // // HTTP | gRPC // -----|----- // `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` // `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: // "123456")` // // ## Rules for HTTP mapping // // 1. Leaf request fields (recursive expansion nested messages in the request // message) are classified into three categories: // - Fields referred by the path template. They are passed via the URL path. // - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP // request body. // - All other fields are passed via the URL query parameters, and the // parameter name is the field path in the request message. A repeated // field can be represented as multiple query parameters under the same // name. // 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields // are passed via URL path and HTTP request body. // 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all // fields are passed via URL path and URL query parameters. // // ### Path template syntax // // Template = "/" Segments [ Verb ] ; // Segments = Segment { "/" Segment } ; // Segment = "*" | "**" | LITERAL | Variable ; // Variable = "{" FieldPath [ "=" Segments ] "}" ; // FieldPath = IDENT { "." IDENT } ; // Verb = ":" LITERAL ; // // The syntax `*` matches a single URL path segment. The syntax `**` matches // zero or more URL path segments, which must be the last part of the URL path // except the `Verb`. // // The syntax `Variable` matches part of the URL path as specified by its // template. A variable template must not contain other variables. If a variable // matches a single path segment, its template may be omitted, e.g. `{var}` // is equivalent to `{var=*}`. // // The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` // contains any reserved character, such characters should be percent-encoded // before the matching. // // If a variable contains exactly one path segment, such as `"{var}"` or // `"{var=*}"`, when such a variable is expanded into a URL path on the client // side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The // server side does the reverse decoding. Such variables show up in the // [Discovery // Document](https://developers.google.com/discovery/v1/reference/apis) as // `{var}`. // // If a variable contains multiple path segments, such as `"{var=foo/*}"` // or `"{var=**}"`, when such a variable is expanded into a URL path on the // client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. // The server side does the reverse decoding, except "%2F" and "%2f" are left // unchanged. Such variables show up in the // [Discovery // Document](https://developers.google.com/discovery/v1/reference/apis) as // `{+var}`. // // ## Using gRPC API Service Configuration // // gRPC API Service Configuration (service config) is a configuration language // for configuring a gRPC service to become a user-facing product. The // service config is simply the YAML representation of the `google.api.Service` // proto message. // // As an alternative to annotating your proto file, you can configure gRPC // transcoding in your service config YAML files. You do this by specifying a // `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same // effect as the proto annotation. This can be particularly useful if you // have a proto that is reused in multiple services. Note that any transcoding // specified in the service config will override any matching transcoding // configuration in the proto. // // Example: // // http: // rules: // # Selects a gRPC method and applies HttpRule to it. // - selector: example.v1.Messaging.GetMessage // get: /v1/messages/{message_id}/{sub.subfield} // // ## Special notes // // When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the // proto to JSON conversion must follow the [proto3 // specification](https://developers.google.com/protocol-buffers/docs/proto3#json). // // While the single segment variable follows the semantics of // [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String // Expansion, the multi segment variable **does not** follow RFC 6570 Section // 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion // does not expand special characters like `?` and `#`, which would lead // to invalid URLs. As the result, gRPC Transcoding uses a custom encoding // for multi segment variables. // // The path variables **must not** refer to any repeated or mapped field, // because client libraries are not capable of handling such variable expansion. // // The path variables **must not** capture the leading "/" character. The reason // is that the most common use case "{var}" does not capture the leading "/" // character. For consistency, all path variables must share the same behavior. // // Repeated message fields must not be mapped to URL query parameters, because // no client library can support such complicated mapping. // // If an API needs to use a JSON array for request or response body, it can map // the request or response body to a repeated field. However, some gRPC // Transcoding implementations may not support this feature. message HttpRule { // Selects a method to which this rule applies. // // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. string selector = 1; // Determines the URL pattern is matched by this rules. This pattern can be // used with any of the {get|put|post|delete|patch} methods. A custom method // can be defined using the 'custom' field. oneof pattern { // Maps to HTTP GET. Used for listing and getting information about // resources. string get = 2; // Maps to HTTP PUT. Used for replacing a resource. string put = 3; // Maps to HTTP POST. Used for creating a resource or performing an action. string post = 4; // Maps to HTTP DELETE. Used for deleting a resource. string delete = 5; // Maps to HTTP PATCH. Used for updating a resource. string patch = 6; // The custom pattern is used for specifying an HTTP method that is not // included in the `pattern` field, such as HEAD, or "*" to leave the // HTTP method unspecified for this rule. The wild-card rule is useful // for services that provide content to Web (HTML) clients. CustomHttpPattern custom = 8; } // The name of the request field whose value is mapped to the HTTP request // body, or `*` for mapping all request fields not captured by the path // pattern to the HTTP body, or omitted for not having any HTTP request body. // // NOTE: the referred field must be present at the top-level of the request // message type. string body = 7; // Optional. The name of the response field whose value is mapped to the HTTP // response body. When omitted, the entire response message will be used // as the HTTP response body. // // NOTE: The referred field must be present at the top-level of the response // message type. string response_body = 12; // Additional HTTP bindings for the selector. Nested bindings must // not contain an `additional_bindings` field themselves (that is, // the nesting may only be one level deep). repeated HttpRule additional_bindings = 11; } // A custom pattern is used for defining custom HTTP verb. message CustomHttpPattern { // The name of this custom HTTP verb. string kind = 1; // The path matched by this custom verb. string path = 2; } ================================================ FILE: third_party/google/api/httpbody.proto ================================================ // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google.api; import "google/protobuf/any.proto"; option cc_enable_arenas = true; option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; option java_multiple_files = true; option java_outer_classname = "HttpBodyProto"; option java_package = "com.google.api"; option objc_class_prefix = "GAPI"; // Message that represents an arbitrary HTTP body. It should only be used for // payload formats that can't be represented as JSON, such as raw binary or // an HTML page. // // // This message can be used both in streaming and non-streaming API methods in // the request as well as the response. // // It can be used as a top-level request field, which is convenient if one // wants to extract parameters from either the URL or HTTP template into the // request fields and also want access to the raw HTTP body. // // Example: // // message GetResourceRequest { // // A unique request id. // string request_id = 1; // // // The raw HTTP body is bound to this field. // google.api.HttpBody http_body = 2; // } // // service ResourceService { // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); // rpc UpdateResource(google.api.HttpBody) returns // (google.protobuf.Empty); // } // // Example with streaming methods: // // service CaldavService { // rpc GetCalendar(stream google.api.HttpBody) // returns (stream google.api.HttpBody); // rpc UpdateCalendar(stream google.api.HttpBody) // returns (stream google.api.HttpBody); // } // // Use of this type only changes how the request and response bodies are // handled, all other features will continue to work unchanged. message HttpBody { // The HTTP Content-Type header value specifying the content type of the body. string content_type = 1; // The HTTP request/response body as raw binary. bytes data = 2; // Application specific response metadata. Must be set in the first response // for streaming APIs. repeated google.protobuf.Any extensions = 3; } ================================================ FILE: third_party/google/protobuf/descriptor.proto ================================================ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Author: kenton@google.com (Kenton Varda) // Based on original Protocol Buffers design by // Sanjay Ghemawat, Jeff Dean, and others. // // The messages in this file describe the definitions found in .proto files. // A valid .proto file can be translated directly to a FileDescriptorProto // without any other information (e.g. without reading its imports). syntax = "proto2"; package google.protobuf; option go_package = "google.golang.org/protobuf/types/descriptorpb"; option java_package = "com.google.protobuf"; option java_outer_classname = "DescriptorProtos"; option csharp_namespace = "Google.Protobuf.Reflection"; option objc_class_prefix = "GPB"; option cc_enable_arenas = true; // descriptor.proto must be optimized for speed because reflection-based // algorithms don't work during bootstrapping. option optimize_for = SPEED; // The protocol compiler can output a FileDescriptorSet containing the .proto // files it parses. message FileDescriptorSet { repeated FileDescriptorProto file = 1; } // Describes a complete .proto file. message FileDescriptorProto { optional string name = 1; // file name, relative to root of source tree optional string package = 2; // e.g. "foo", "foo.bar", etc. // Names of files imported by this file. repeated string dependency = 3; // Indexes of the public imported files in the dependency list above. repeated int32 public_dependency = 10; // Indexes of the weak imported files in the dependency list. // For Google-internal migration only. Do not use. repeated int32 weak_dependency = 11; // All top-level definitions in this file. repeated DescriptorProto message_type = 4; repeated EnumDescriptorProto enum_type = 5; repeated ServiceDescriptorProto service = 6; repeated FieldDescriptorProto extension = 7; optional FileOptions options = 8; // This field contains optional information about the original source code. // You may safely remove this entire field without harming runtime // functionality of the descriptors -- the information is needed only by // development tools. optional SourceCodeInfo source_code_info = 9; // The syntax of the proto file. // The supported values are "proto2" and "proto3". optional string syntax = 12; } // Describes a message type. message DescriptorProto { optional string name = 1; repeated FieldDescriptorProto field = 2; repeated FieldDescriptorProto extension = 6; repeated DescriptorProto nested_type = 3; repeated EnumDescriptorProto enum_type = 4; message ExtensionRange { optional int32 start = 1; // Inclusive. optional int32 end = 2; // Exclusive. optional ExtensionRangeOptions options = 3; } repeated ExtensionRange extension_range = 5; repeated OneofDescriptorProto oneof_decl = 8; optional MessageOptions options = 7; // Range of reserved tag numbers. Reserved tag numbers may not be used by // fields or extension ranges in the same message. Reserved ranges may // not overlap. message ReservedRange { optional int32 start = 1; // Inclusive. optional int32 end = 2; // Exclusive. } repeated ReservedRange reserved_range = 9; // Reserved field names, which may not be used by fields in the same message. // A given name may only be reserved once. repeated string reserved_name = 10; } message ExtensionRangeOptions { // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } // Describes a field within a message. message FieldDescriptorProto { enum Type { // 0 is reserved for errors. // Order is weird for historical reasons. TYPE_DOUBLE = 1; TYPE_FLOAT = 2; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if // negative values are likely. TYPE_INT64 = 3; TYPE_UINT64 = 4; // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if // negative values are likely. TYPE_INT32 = 5; TYPE_FIXED64 = 6; TYPE_FIXED32 = 7; TYPE_BOOL = 8; TYPE_STRING = 9; // Tag-delimited aggregate. // Group type is deprecated and not supported in proto3. However, Proto3 // implementations should still be able to parse the group wire format and // treat group fields as unknown fields. TYPE_GROUP = 10; TYPE_MESSAGE = 11; // Length-delimited aggregate. // New in version 2. TYPE_BYTES = 12; TYPE_UINT32 = 13; TYPE_ENUM = 14; TYPE_SFIXED32 = 15; TYPE_SFIXED64 = 16; TYPE_SINT32 = 17; // Uses ZigZag encoding. TYPE_SINT64 = 18; // Uses ZigZag encoding. } enum Label { // 0 is reserved for errors LABEL_OPTIONAL = 1; LABEL_REQUIRED = 2; LABEL_REPEATED = 3; } optional string name = 1; optional int32 number = 3; optional Label label = 4; // If type_name is set, this need not be set. If both this and type_name // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. optional Type type = 5; // For message and enum types, this is the name of the type. If the name // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping // rules are used to find the type (i.e. first the nested types within this // message are searched, then within the parent, on up to the root // namespace). optional string type_name = 6; // For extensions, this is the name of the type being extended. It is // resolved in the same manner as type_name. optional string extendee = 2; // For numeric types, contains the original text representation of the value. // For booleans, "true" or "false". // For strings, contains the default text contents (not escaped in any way). // For bytes, contains the C escaped value. All bytes >= 128 are escaped. // TODO(kenton): Base-64 encode? optional string default_value = 7; // If set, gives the index of a oneof in the containing type's oneof_decl // list. This field is a member of that oneof. optional int32 oneof_index = 9; // JSON name of this field. The value is set by protocol compiler. If the // user has set a "json_name" option on this field, that option's value // will be used. Otherwise, it's deduced from the field's name by converting // it to camelCase. optional string json_name = 10; optional FieldOptions options = 8; // If true, this is a proto3 "optional". When a proto3 field is optional, it // tracks presence regardless of field type. // // When proto3_optional is true, this field must be belong to a oneof to // signal to old proto3 clients that presence is tracked for this field. This // oneof is known as a "synthetic" oneof, and this field must be its sole // member (each proto3 optional field gets its own synthetic oneof). Synthetic // oneofs exist in the descriptor only, and do not generate any API. Synthetic // oneofs must be ordered after all "real" oneofs. // // For message fields, proto3_optional doesn't create any semantic change, // since non-repeated message fields always track presence. However it still // indicates the semantic detail of whether the user wrote "optional" or not. // This can be useful for round-tripping the .proto file. For consistency we // give message fields a synthetic oneof also, even though it is not required // to track presence. This is especially important because the parser can't // tell if a field is a message or an enum, so it must always create a // synthetic oneof. // // Proto2 optional fields do not set this flag, because they already indicate // optional with `LABEL_OPTIONAL`. optional bool proto3_optional = 17; } // Describes a oneof. message OneofDescriptorProto { optional string name = 1; optional OneofOptions options = 2; } // Describes an enum type. message EnumDescriptorProto { optional string name = 1; repeated EnumValueDescriptorProto value = 2; optional EnumOptions options = 3; // Range of reserved numeric values. Reserved values may not be used by // entries in the same enum. Reserved ranges may not overlap. // // Note that this is distinct from DescriptorProto.ReservedRange in that it // is inclusive such that it can appropriately represent the entire int32 // domain. message EnumReservedRange { optional int32 start = 1; // Inclusive. optional int32 end = 2; // Inclusive. } // Range of reserved numeric values. Reserved numeric values may not be used // by enum values in the same enum declaration. Reserved ranges may not // overlap. repeated EnumReservedRange reserved_range = 4; // Reserved enum value names, which may not be reused. A given name may only // be reserved once. repeated string reserved_name = 5; } // Describes a value within an enum. message EnumValueDescriptorProto { optional string name = 1; optional int32 number = 2; optional EnumValueOptions options = 3; } // Describes a service. message ServiceDescriptorProto { optional string name = 1; repeated MethodDescriptorProto method = 2; optional ServiceOptions options = 3; } // Describes a method of a service. message MethodDescriptorProto { optional string name = 1; // Input and output type names. These are resolved in the same way as // FieldDescriptorProto.type_name, but must refer to a message type. optional string input_type = 2; optional string output_type = 3; optional MethodOptions options = 4; // Identifies if client streams multiple client messages optional bool client_streaming = 5 [default = false]; // Identifies if server streams multiple server messages optional bool server_streaming = 6 [default = false]; } // =================================================================== // Options // Each of the definitions above may have "options" attached. These are // just annotations which may cause code to be generated slightly differently // or may contain hints for code that manipulates protocol messages. // // Clients may define custom options as extensions of the *Options messages. // These extensions may not yet be known at parsing time, so the parser cannot // store the values in them. Instead it stores them in a field in the *Options // message called uninterpreted_option. This field must have the same name // across all *Options messages. We then use this field to populate the // extensions when we build a descriptor, at which point all protos have been // parsed and so all extensions are known. // // Extension numbers for custom options may be chosen as follows: // * For options which will only be used within a single application or // organization, or for experimental options, use field numbers 50000 // through 99999. It is up to you to ensure that you do not use the // same number for multiple options. // * For options which will be published and used publicly by multiple // independent entities, e-mail protobuf-global-extension-registry@google.com // to reserve extension numbers. Simply provide your project name (e.g. // Objective-C plugin) and your project website (if available) -- there's no // need to explain how you intend to use them. Usually you only need one // extension number. You can declare multiple options with only one extension // number by putting them in a sub-message. See the Custom Options section of // the docs for examples: // https://developers.google.com/protocol-buffers/docs/proto#options // If this turns out to be popular, a web service will be set up // to automatically assign option numbers. message FileOptions { // Sets the Java package where classes generated from this .proto will be // placed. By default, the proto package is used, but this is often // inappropriate because proto packages do not normally start with backwards // domain names. optional string java_package = 1; // Controls the name of the wrapper Java class generated for the .proto file. // That class will always contain the .proto file's getDescriptor() method as // well as any top-level extensions defined in the .proto file. // If java_multiple_files is disabled, then all the other classes from the // .proto file will be nested inside the single wrapper outer class. optional string java_outer_classname = 8; // If enabled, then the Java code generator will generate a separate .java // file for each top-level message, enum, and service defined in the .proto // file. Thus, these types will *not* be nested inside the wrapper class // named by java_outer_classname. However, the wrapper class will still be // generated to contain the file's getDescriptor() method as well as any // top-level extensions defined in the file. optional bool java_multiple_files = 10 [default = false]; // This option does nothing. optional bool java_generate_equals_and_hash = 20 [deprecated=true]; // If set true, then the Java2 code generator will generate code that // throws an exception whenever an attempt is made to assign a non-UTF-8 // byte sequence to a string field. // Message reflection will do the same. // However, an extension field still accepts non-UTF-8 byte sequences. // This option has no effect on when used with the lite runtime. optional bool java_string_check_utf8 = 27 [default = false]; // Generated classes can be optimized for speed or code size. enum OptimizeMode { SPEED = 1; // Generate complete code for parsing, serialization, // etc. CODE_SIZE = 2; // Use ReflectionOps to implement these methods. LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. } optional OptimizeMode optimize_for = 9 [default = SPEED]; // Sets the Go package where structs generated from this .proto will be // placed. If omitted, the Go package will be derived from the following: // - The basename of the package import path, if provided. // - Otherwise, the package statement in the .proto file, if present. // - Otherwise, the basename of the .proto file, without extension. optional string go_package = 11; // Should generic services be generated in each language? "Generic" services // are not specific to any particular RPC system. They are generated by the // main code generators in each language (without additional plugins). // Generic services were the only kind of service generation supported by // early versions of google.protobuf. // // Generic services are now considered deprecated in favor of using plugins // that generate code specific to your particular RPC system. Therefore, // these default to false. Old code which depends on generic services should // explicitly set them to true. optional bool cc_generic_services = 16 [default = false]; optional bool java_generic_services = 17 [default = false]; optional bool py_generic_services = 18 [default = false]; optional bool php_generic_services = 42 [default = false]; // Is this file deprecated? // Depending on the target platform, this can emit Deprecated annotations // for everything in the file, or it will be completely ignored; in the very // least, this is a formalization for deprecating files. optional bool deprecated = 23 [default = false]; // Enables the use of arenas for the proto messages in this file. This applies // only to generated classes for C++. optional bool cc_enable_arenas = 31 [default = true]; // Sets the objective c class prefix which is prepended to all objective c // generated classes from this .proto. There is no default. optional string objc_class_prefix = 36; // Namespace for generated classes; defaults to the package. optional string csharp_namespace = 37; // By default Swift generators will take the proto package and CamelCase it // replacing '.' with underscore and use that to prefix the types/symbols // defined. When this options is provided, they will use this value instead // to prefix the types/symbols defined. optional string swift_prefix = 39; // Sets the php class prefix which is prepended to all php generated classes // from this .proto. Default is empty. optional string php_class_prefix = 40; // Use this option to change the namespace of php generated classes. Default // is empty. When this option is empty, the package name will be used for // determining the namespace. optional string php_namespace = 41; // Use this option to change the namespace of php generated metadata classes. // Default is empty. When this option is empty, the proto file name will be // used for determining the namespace. optional string php_metadata_namespace = 44; // Use this option to change the package of ruby generated classes. Default // is empty. When this option is not set, the package name will be used for // determining the ruby package. optional string ruby_package = 45; // The parser stores options it doesn't recognize here. // See the documentation for the "Options" section above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. // See the documentation for the "Options" section above. extensions 1000 to max; reserved 38; } message MessageOptions { // Set true to use the old proto1 MessageSet wire format for extensions. // This is provided for backwards-compatibility with the MessageSet wire // format. You should not use this for any other reason: It's less // efficient, has fewer features, and is more complicated. // // The message must be defined exactly as follows: // message Foo { // option message_set_wire_format = true; // extensions 4 to max; // } // Note that the message cannot have any defined fields; MessageSets only // have extensions. // // All extensions of your type must be singular messages; e.g. they cannot // be int32s, enums, or repeated messages. // // Because this is an option, the above two restrictions are not enforced by // the protocol compiler. optional bool message_set_wire_format = 1 [default = false]; // Disables the generation of the standard "descriptor()" accessor, which can // conflict with a field of the same name. This is meant to make migration // from proto1 easier; new code should avoid fields named "descriptor". optional bool no_standard_descriptor_accessor = 2 [default = false]; // Is this message deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the message, or it will be completely ignored; in the very least, // this is a formalization for deprecating messages. optional bool deprecated = 3 [default = false]; reserved 4, 5, 6; // Whether the message is an automatically generated map entry type for the // maps field. // // For maps fields: // map map_field = 1; // The parsed descriptor looks like: // message MapFieldEntry { // option map_entry = true; // optional KeyType key = 1; // optional ValueType value = 2; // } // repeated MapFieldEntry map_field = 1; // // Implementations may choose not to generate the map_entry=true message, but // use a native map in the target language to hold the keys and values. // The reflection APIs in such implementations still need to work as // if the field is a repeated message field. // // NOTE: Do not set the option in .proto files. Always use the maps syntax // instead. The option should only be implicitly set by the proto compiler // parser. optional bool map_entry = 7; reserved 8; // javalite_serializable reserved 9; // javanano_as_lite // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message FieldOptions { // The ctype option instructs the C++ code generator to use a different // representation of the field than it normally would. See the specific // options below. This option is not yet implemented in the open source // release -- sorry, we'll try to include it in a future version! optional CType ctype = 1 [default = STRING]; enum CType { // Default mode. STRING = 0; CORD = 1; STRING_PIECE = 2; } // The packed option can be enabled for repeated primitive fields to enable // a more efficient representation on the wire. Rather than repeatedly // writing the tag and type for each element, the entire array is encoded as // a single length-delimited blob. In proto3, only explicit setting it to // false will avoid using packed encoding. optional bool packed = 2; // The jstype option determines the JavaScript type used for values of the // field. The option is permitted only for 64 bit integral and fixed types // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING // is represented as JavaScript string, which avoids loss of precision that // can happen when a large value is converted to a floating point JavaScript. // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to // use the JavaScript "number" type. The behavior of the default option // JS_NORMAL is implementation dependent. // // This option is an enum to permit additional types to be added, e.g. // goog.math.Integer. optional JSType jstype = 6 [default = JS_NORMAL]; enum JSType { // Use the default type. JS_NORMAL = 0; // Use JavaScript strings. JS_STRING = 1; // Use JavaScript numbers. JS_NUMBER = 2; } // Should this field be parsed lazily? Lazy applies only to message-type // fields. It means that when the outer message is initially parsed, the // inner message's contents will not be parsed but instead stored in encoded // form. The inner message will actually be parsed when it is first accessed. // // This is only a hint. Implementations are free to choose whether to use // eager or lazy parsing regardless of the value of this option. However, // setting this option true suggests that the protocol author believes that // using lazy parsing on this field is worth the additional bookkeeping // overhead typically needed to implement it. // // This option does not affect the public interface of any generated code; // all method signatures remain the same. Furthermore, thread-safety of the // interface is not affected by this option; const methods remain safe to // call from multiple threads concurrently, while non-const methods continue // to require exclusive access. // // // Note that implementations may choose not to check required fields within // a lazy sub-message. That is, calling IsInitialized() on the outer message // may return true even if the inner message has missing required fields. // This is necessary because otherwise the inner message would have to be // parsed in order to perform the check, defeating the purpose of lazy // parsing. An implementation which chooses not to check required fields // must be consistent about it. That is, for any particular sub-message, the // implementation must either *always* check its required fields, or *never* // check its required fields, regardless of whether or not the message has // been parsed. optional bool lazy = 5 [default = false]; // Is this field deprecated? // Depending on the target platform, this can emit Deprecated annotations // for accessors, or it will be completely ignored; in the very least, this // is a formalization for deprecating fields. optional bool deprecated = 3 [default = false]; // For Google-internal migration only. Do not use. optional bool weak = 10 [default = false]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; reserved 4; // removed jtype } message OneofOptions { // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message EnumOptions { // Set this option to true to allow mapping different tag names to the same // value. optional bool allow_alias = 2; // Is this enum deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the enum, or it will be completely ignored; in the very least, this // is a formalization for deprecating enums. optional bool deprecated = 3 [default = false]; reserved 5; // javanano_as_lite // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message EnumValueOptions { // Is this enum value deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the enum value, or it will be completely ignored; in the very least, // this is a formalization for deprecating enum values. optional bool deprecated = 1 [default = false]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message ServiceOptions { // Note: Field numbers 1 through 32 are reserved for Google's internal RPC // framework. We apologize for hoarding these numbers to ourselves, but // we were already using them long before we decided to release Protocol // Buffers. // Is this service deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the service, or it will be completely ignored; in the very least, // this is a formalization for deprecating services. optional bool deprecated = 33 [default = false]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } message MethodOptions { // Note: Field numbers 1 through 32 are reserved for Google's internal RPC // framework. We apologize for hoarding these numbers to ourselves, but // we were already using them long before we decided to release Protocol // Buffers. // Is this method deprecated? // Depending on the target platform, this can emit Deprecated annotations // for the method, or it will be completely ignored; in the very least, // this is a formalization for deprecating methods. optional bool deprecated = 33 [default = false]; // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, // or neither? HTTP based RPC implementation may choose GET verb for safe // methods, and PUT verb for idempotent methods instead of the default POST. enum IdempotencyLevel { IDEMPOTENCY_UNKNOWN = 0; NO_SIDE_EFFECTS = 1; // implies idempotent IDEMPOTENT = 2; // idempotent, but may have side effects } optional IdempotencyLevel idempotency_level = 34 [default = IDEMPOTENCY_UNKNOWN]; // The parser stores options it doesn't recognize here. See above. repeated UninterpretedOption uninterpreted_option = 999; // Clients can define custom options in extensions of this message. See above. extensions 1000 to max; } // A message representing a option the parser does not recognize. This only // appears in options protos created by the compiler::Parser class. // DescriptorPool resolves these when building Descriptor objects. Therefore, // options protos in descriptor objects (e.g. returned by Descriptor::options(), // or produced by Descriptor::CopyTo()) will never have UninterpretedOptions // in them. message UninterpretedOption { // The name of the uninterpreted option. Each string represents a segment in // a dot-separated name. is_extension is true iff a segment represents an // extension (denoted with parentheses in options specs in .proto files). // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents // "foo.(bar.baz).qux". message NamePart { required string name_part = 1; required bool is_extension = 2; } repeated NamePart name = 2; // The value of the uninterpreted option, in whatever type the tokenizer // identified it as during parsing. Exactly one of these should be set. optional string identifier_value = 3; optional uint64 positive_int_value = 4; optional int64 negative_int_value = 5; optional double double_value = 6; optional bytes string_value = 7; optional string aggregate_value = 8; } // =================================================================== // Optional source code info // Encapsulates information about the original source file from which a // FileDescriptorProto was generated. message SourceCodeInfo { // A Location identifies a piece of source code in a .proto file which // corresponds to a particular definition. This information is intended // to be useful to IDEs, code indexers, documentation generators, and similar // tools. // // For example, say we have a file like: // message Foo { // optional string foo = 1; // } // Let's look at just the field definition: // optional string foo = 1; // ^ ^^ ^^ ^ ^^^ // a bc de f ghi // We have the following locations: // span path represents // [a,i) [ 4, 0, 2, 0 ] The whole field definition. // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). // // Notes: // - A location may refer to a repeated field itself (i.e. not to any // particular index within it). This is used whenever a set of elements are // logically enclosed in a single code segment. For example, an entire // extend block (possibly containing multiple extension definitions) will // have an outer location whose path refers to the "extensions" repeated // field without an index. // - Multiple locations may have the same path. This happens when a single // logical declaration is spread out across multiple places. The most // obvious example is the "extend" block again -- there may be multiple // extend blocks in the same scope, each of which will have the same path. // - A location's span is not always a subset of its parent's span. For // example, the "extendee" of an extension declaration appears at the // beginning of the "extend" block and is shared by all extensions within // the block. // - Just because a location's span is a subset of some other location's span // does not mean that it is a descendant. For example, a "group" defines // both a type and a field in a single declaration. Thus, the locations // corresponding to the type and field and their components will overlap. // - Code which tries to interpret locations should probably be designed to // ignore those that it doesn't understand, as more types of locations could // be recorded in the future. repeated Location location = 1; message Location { // Identifies which part of the FileDescriptorProto was defined at this // location. // // Each element is a field number or an index. They form a path from // the root FileDescriptorProto to the place where the definition occurs. For // example, this path: // [ 4, 3, 2, 7, 1 ] // refers to: // file.message_type(3) // 4, 3 // .field(7) // 2, 7 // .name() // 1 // This is because FileDescriptorProto.message_type has field number 4: // repeated DescriptorProto message_type = 4; // and DescriptorProto.field has field number 2: // repeated FieldDescriptorProto field = 2; // and FieldDescriptorProto.name has field number 1: // optional string name = 1; // // Thus, the above path gives the location of a field name. If we removed // the last element: // [ 4, 3, 2, 7 ] // this path refers to the whole field declaration (from the beginning // of the label to the terminating semicolon). repeated int32 path = 1 [packed = true]; // Always has exactly three or four elements: start line, start column, // end line (optional, otherwise assumed same as start line), end column. // These are packed into a single field for efficiency. Note that line // and column numbers are zero-based -- typically you will want to add // 1 to each before displaying to a user. repeated int32 span = 2 [packed = true]; // If this SourceCodeInfo represents a complete declaration, these are any // comments appearing before and after the declaration which appear to be // attached to the declaration. // // A series of line comments appearing on consecutive lines, with no other // tokens appearing on those lines, will be treated as a single comment. // // leading_detached_comments will keep paragraphs of comments that appear // before (but not connected to) the current element. Each paragraph, // separated by empty lines, will be one comment element in the repeated // field. // // Only the comment content is provided; comment markers (e.g. //) are // stripped out. For block comments, leading whitespace and an asterisk // will be stripped from the beginning of each line other than the first. // Newlines are included in the output. // // Examples: // // optional int32 foo = 1; // Comment attached to foo. // // Comment attached to bar. // optional int32 bar = 2; // // optional string baz = 3; // // Comment attached to baz. // // Another line attached to baz. // // // Comment attached to qux. // // // // Another line attached to qux. // optional double qux = 4; // // // Detached comment for corge. This is not leading or trailing comments // // to qux or corge because there are blank lines separating it from // // both. // // // Detached comment for corge paragraph 2. // // optional string corge = 5; // /* Block comment attached // * to corge. Leading asterisks // * will be removed. */ // /* Block comment attached to // * grault. */ // optional int32 grault = 6; // // // ignored detached comments. optional string leading_comments = 3; optional string trailing_comments = 4; repeated string leading_detached_comments = 6; } } // Describes the relationship between generated code and its original source // file. A GeneratedCodeInfo message is associated with only one generated // source file, but may contain references to different source .proto files. message GeneratedCodeInfo { // An Annotation connects some span of text in generated code to an element // of its generating .proto file. repeated Annotation annotation = 1; message Annotation { // Identifies the element in the original source .proto file. This field // is formatted the same as SourceCodeInfo.Location.path. repeated int32 path = 1 [packed = true]; // Identifies the filesystem path to the original source .proto. optional string source_file = 2; // Identifies the starting offset in bytes in the generated code // that relates to the identified object. optional int32 begin = 3; // Identifies the ending offset in bytes in the generated code that // relates to the identified offset. The end offset should be one past // the last relevant byte (so the length of the text = end - begin). optional int32 end = 4; } } ================================================ FILE: third_party/validate/README.md ================================================ # protoc-gen-validate (PGV) * https://github.com/envoyproxy/protoc-gen-validate ================================================ FILE: third_party/validate/validate.proto ================================================ syntax = "proto2"; package validate; option go_package = "github.com/envoyproxy/protoc-gen-validate/validate"; option java_package = "io.envoyproxy.pgv.validate"; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; // Validation rules applied at the message level extend google.protobuf.MessageOptions { // Disabled nullifies any validation rules for this message, including any // message fields associated with it that do support validation. optional bool disabled = 1071; // Ignore skips generation of validation methods for this message. optional bool ignored = 1072; } // Validation rules applied at the oneof level extend google.protobuf.OneofOptions { // Required ensures that exactly one the field options in a oneof is set; // validation fails if no fields in the oneof are set. optional bool required = 1071; } // Validation rules applied at the field level extend google.protobuf.FieldOptions { // Rules specify the validations to be performed on this field. By default, // no validation is performed against a field. optional FieldRules rules = 1071; } // FieldRules encapsulates the rules for each type of field. Depending on the // field, the correct set should be used to ensure proper validations. message FieldRules { optional MessageRules message = 17; oneof type { // Scalar Field Types FloatRules float = 1; DoubleRules double = 2; Int32Rules int32 = 3; Int64Rules int64 = 4; UInt32Rules uint32 = 5; UInt64Rules uint64 = 6; SInt32Rules sint32 = 7; SInt64Rules sint64 = 8; Fixed32Rules fixed32 = 9; Fixed64Rules fixed64 = 10; SFixed32Rules sfixed32 = 11; SFixed64Rules sfixed64 = 12; BoolRules bool = 13; StringRules string = 14; BytesRules bytes = 15; // Complex Field Types EnumRules enum = 16; RepeatedRules repeated = 18; MapRules map = 19; // Well-Known Field Types AnyRules any = 20; DurationRules duration = 21; TimestampRules timestamp = 22; } } // FloatRules describes the constraints applied to `float` values message FloatRules { // Const specifies that this field must be exactly the specified value optional float const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional float lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional float lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional float gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional float gte = 5; // In specifies that this field must be equal to one of the specified // values repeated float in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated float not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // DoubleRules describes the constraints applied to `double` values message DoubleRules { // Const specifies that this field must be exactly the specified value optional double const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional double lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional double lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional double gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional double gte = 5; // In specifies that this field must be equal to one of the specified // values repeated double in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated double not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Int32Rules describes the constraints applied to `int32` values message Int32Rules { // Const specifies that this field must be exactly the specified value optional int32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional int32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional int32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional int32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional int32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated int32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated int32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Int64Rules describes the constraints applied to `int64` values message Int64Rules { // Const specifies that this field must be exactly the specified value optional int64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional int64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional int64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional int64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional int64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated int64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated int64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // UInt32Rules describes the constraints applied to `uint32` values message UInt32Rules { // Const specifies that this field must be exactly the specified value optional uint32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional uint32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional uint32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional uint32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional uint32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated uint32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated uint32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // UInt64Rules describes the constraints applied to `uint64` values message UInt64Rules { // Const specifies that this field must be exactly the specified value optional uint64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional uint64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional uint64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional uint64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional uint64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated uint64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated uint64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SInt32Rules describes the constraints applied to `sint32` values message SInt32Rules { // Const specifies that this field must be exactly the specified value optional sint32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sint32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sint32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sint32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sint32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sint32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sint32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SInt64Rules describes the constraints applied to `sint64` values message SInt64Rules { // Const specifies that this field must be exactly the specified value optional sint64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sint64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sint64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sint64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sint64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sint64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sint64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Fixed32Rules describes the constraints applied to `fixed32` values message Fixed32Rules { // Const specifies that this field must be exactly the specified value optional fixed32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional fixed32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional fixed32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional fixed32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional fixed32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated fixed32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated fixed32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // Fixed64Rules describes the constraints applied to `fixed64` values message Fixed64Rules { // Const specifies that this field must be exactly the specified value optional fixed64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional fixed64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional fixed64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional fixed64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional fixed64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated fixed64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated fixed64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SFixed32Rules describes the constraints applied to `sfixed32` values message SFixed32Rules { // Const specifies that this field must be exactly the specified value optional sfixed32 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sfixed32 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sfixed32 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sfixed32 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sfixed32 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sfixed32 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sfixed32 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // SFixed64Rules describes the constraints applied to `sfixed64` values message SFixed64Rules { // Const specifies that this field must be exactly the specified value optional sfixed64 const = 1; // Lt specifies that this field must be less than the specified value, // exclusive optional sfixed64 lt = 2; // Lte specifies that this field must be less than or equal to the // specified value, inclusive optional sfixed64 lte = 3; // Gt specifies that this field must be greater than the specified value, // exclusive. If the value of Gt is larger than a specified Lt or Lte, the // range is reversed. optional sfixed64 gt = 4; // Gte specifies that this field must be greater than or equal to the // specified value, inclusive. If the value of Gte is larger than a // specified Lt or Lte, the range is reversed. optional sfixed64 gte = 5; // In specifies that this field must be equal to one of the specified // values repeated sfixed64 in = 6; // NotIn specifies that this field cannot be equal to one of the specified // values repeated sfixed64 not_in = 7; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 8; } // BoolRules describes the constraints applied to `bool` values message BoolRules { // Const specifies that this field must be exactly the specified value optional bool const = 1; } // StringRules describe the constraints applied to `string` values message StringRules { // Const specifies that this field must be exactly the specified value optional string const = 1; // Len specifies that this field must be the specified number of // characters (Unicode code points). Note that the number of // characters may differ from the number of bytes in the string. optional uint64 len = 19; // MinLen specifies that this field must be the specified number of // characters (Unicode code points) at a minimum. Note that the number of // characters may differ from the number of bytes in the string. optional uint64 min_len = 2; // MaxLen specifies that this field must be the specified number of // characters (Unicode code points) at a maximum. Note that the number of // characters may differ from the number of bytes in the string. optional uint64 max_len = 3; // LenBytes specifies that this field must be the specified number of bytes // at a minimum optional uint64 len_bytes = 20; // MinBytes specifies that this field must be the specified number of bytes // at a minimum optional uint64 min_bytes = 4; // MaxBytes specifies that this field must be the specified number of bytes // at a maximum optional uint64 max_bytes = 5; // Pattern specifies that this field must match against the specified // regular expression (RE2 syntax). The included expression should elide // any delimiters. optional string pattern = 6; // Prefix specifies that this field must have the specified substring at // the beginning of the string. optional string prefix = 7; // Suffix specifies that this field must have the specified substring at // the end of the string. optional string suffix = 8; // Contains specifies that this field must have the specified substring // anywhere in the string. optional string contains = 9; // NotContains specifies that this field cannot have the specified substring // anywhere in the string. optional string not_contains = 23; // In specifies that this field must be equal to one of the specified // values repeated string in = 10; // NotIn specifies that this field cannot be equal to one of the specified // values repeated string not_in = 11; // WellKnown rules provide advanced constraints against common string // patterns oneof well_known { // Email specifies that the field must be a valid email address as // defined by RFC 5322 bool email = 12; // Hostname specifies that the field must be a valid hostname as // defined by RFC 1034. This constraint does not support // internationalized domain names (IDNs). bool hostname = 13; // Ip specifies that the field must be a valid IP (v4 or v6) address. // Valid IPv6 addresses should not include surrounding square brackets. bool ip = 14; // Ipv4 specifies that the field must be a valid IPv4 address. bool ipv4 = 15; // Ipv6 specifies that the field must be a valid IPv6 address. Valid // IPv6 addresses should not include surrounding square brackets. bool ipv6 = 16; // Uri specifies that the field must be a valid, absolute URI as defined // by RFC 3986 bool uri = 17; // UriRef specifies that the field must be a valid URI as defined by RFC // 3986 and may be relative or absolute. bool uri_ref = 18; // Address specifies that the field must be either a valid hostname as // defined by RFC 1034 (which does not support internationalized domain // names or IDNs), or it can be a valid IP (v4 or v6). bool address = 21; // Uuid specifies that the field must be a valid UUID as defined by // RFC 4122 bool uuid = 22; // WellKnownRegex specifies a common well known pattern defined as a regex. KnownRegex well_known_regex = 24; } // This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable // strict header validation. // By default, this is true, and HTTP header validations are RFC-compliant. // Setting to false will enable a looser validations that only disallows // \r\n\0 characters, which can be used to bypass header matching rules. optional bool strict = 25 [default = true]; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 26; } // WellKnownRegex contain some well-known patterns. enum KnownRegex { UNKNOWN = 0; // HTTP header name as defined by RFC 7230. HTTP_HEADER_NAME = 1; // HTTP header value as defined by RFC 7230. HTTP_HEADER_VALUE = 2; } // BytesRules describe the constraints applied to `bytes` values message BytesRules { // Const specifies that this field must be exactly the specified value optional bytes const = 1; // Len specifies that this field must be the specified number of bytes optional uint64 len = 13; // MinLen specifies that this field must be the specified number of bytes // at a minimum optional uint64 min_len = 2; // MaxLen specifies that this field must be the specified number of bytes // at a maximum optional uint64 max_len = 3; // Pattern specifies that this field must match against the specified // regular expression (RE2 syntax). The included expression should elide // any delimiters. optional string pattern = 4; // Prefix specifies that this field must have the specified bytes at the // beginning of the string. optional bytes prefix = 5; // Suffix specifies that this field must have the specified bytes at the // end of the string. optional bytes suffix = 6; // Contains specifies that this field must have the specified bytes // anywhere in the string. optional bytes contains = 7; // In specifies that this field must be equal to one of the specified // values repeated bytes in = 8; // NotIn specifies that this field cannot be equal to one of the specified // values repeated bytes not_in = 9; // WellKnown rules provide advanced constraints against common byte // patterns oneof well_known { // Ip specifies that the field must be a valid IP (v4 or v6) address in // byte format bool ip = 10; // Ipv4 specifies that the field must be a valid IPv4 address in byte // format bool ipv4 = 11; // Ipv6 specifies that the field must be a valid IPv6 address in byte // format bool ipv6 = 12; } // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 14; } // EnumRules describe the constraints applied to enum values message EnumRules { // Const specifies that this field must be exactly the specified value optional int32 const = 1; // DefinedOnly specifies that this field must be only one of the defined // values for this enum, failing on any undefined value. optional bool defined_only = 2; // In specifies that this field must be equal to one of the specified // values repeated int32 in = 3; // NotIn specifies that this field cannot be equal to one of the specified // values repeated int32 not_in = 4; } // MessageRules describe the constraints applied to embedded message values. // For message-type fields, validation is performed recursively. message MessageRules { // Skip specifies that the validation rules of this field should not be // evaluated optional bool skip = 1; // Required specifies that this field must be set optional bool required = 2; } // RepeatedRules describe the constraints applied to `repeated` values message RepeatedRules { // MinItems specifies that this field must have the specified number of // items at a minimum optional uint64 min_items = 1; // MaxItems specifies that this field must have the specified number of // items at a maximum optional uint64 max_items = 2; // Unique specifies that all elements in this field must be unique. This // constraint is only applicable to scalar and enum types (messages are not // supported). optional bool unique = 3; // Items specifies the constraints to be applied to each item in the field. // Repeated message fields will still execute validation against each item // unless skip is specified here. optional FieldRules items = 4; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 5; } // MapRules describe the constraints applied to `map` values message MapRules { // MinPairs specifies that this field must have the specified number of // KVs at a minimum optional uint64 min_pairs = 1; // MaxPairs specifies that this field must have the specified number of // KVs at a maximum optional uint64 max_pairs = 2; // NoSparse specifies values in this field cannot be unset. This only // applies to map's with message value types. optional bool no_sparse = 3; // Keys specifies the constraints to be applied to each key in the field. optional FieldRules keys = 4; // Values specifies the constraints to be applied to the value of each key // in the field. Message values will still have their validations evaluated // unless skip is specified here. optional FieldRules values = 5; // IgnoreEmpty specifies that the validation rules of this field should be // evaluated only if the field is not empty optional bool ignore_empty = 6; } // AnyRules describe constraints applied exclusively to the // `google.protobuf.Any` well-known type message AnyRules { // Required specifies that this field must be set optional bool required = 1; // In specifies that this field's `type_url` must be equal to one of the // specified values. repeated string in = 2; // NotIn specifies that this field's `type_url` must not be equal to any of // the specified values. repeated string not_in = 3; } // DurationRules describe the constraints applied exclusively to the // `google.protobuf.Duration` well-known type message DurationRules { // Required specifies that this field must be set optional bool required = 1; // Const specifies that this field must be exactly the specified value optional google.protobuf.Duration const = 2; // Lt specifies that this field must be less than the specified value, // exclusive optional google.protobuf.Duration lt = 3; // Lt specifies that this field must be less than the specified value, // inclusive optional google.protobuf.Duration lte = 4; // Gt specifies that this field must be greater than the specified value, // exclusive optional google.protobuf.Duration gt = 5; // Gte specifies that this field must be greater than the specified value, // inclusive optional google.protobuf.Duration gte = 6; // In specifies that this field must be equal to one of the specified // values repeated google.protobuf.Duration in = 7; // NotIn specifies that this field cannot be equal to one of the specified // values repeated google.protobuf.Duration not_in = 8; } // TimestampRules describe the constraints applied exclusively to the // `google.protobuf.Timestamp` well-known type message TimestampRules { // Required specifies that this field must be set optional bool required = 1; // Const specifies that this field must be exactly the specified value optional google.protobuf.Timestamp const = 2; // Lt specifies that this field must be less than the specified value, // exclusive optional google.protobuf.Timestamp lt = 3; // Lte specifies that this field must be less than the specified value, // inclusive optional google.protobuf.Timestamp lte = 4; // Gt specifies that this field must be greater than the specified value, // exclusive optional google.protobuf.Timestamp gt = 5; // Gte specifies that this field must be greater than the specified value, // inclusive optional google.protobuf.Timestamp gte = 6; // LtNow specifies that this must be less than the current time. LtNow // can only be used with the Within rule. optional bool lt_now = 7; // GtNow specifies that this must be greater than the current time. GtNow // can only be used with the Within rule. optional bool gt_now = 8; // Within specifies that this field must be within this duration of the // current time. This constraint can be used alone or with the LtNow and // GtNow rules. optional google.protobuf.Duration within = 9; } ================================================ FILE: transport/grpc/balancer.go ================================================ package grpc import ( "google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer/base" "google.golang.org/grpc/metadata" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/transport" ) const ( balancerName = "selector" ) var ( _ base.PickerBuilder = (*balancerBuilder)(nil) _ balancer.Picker = (*balancerPicker)(nil) ) func init() { b := base.NewBalancerBuilder( balancerName, &balancerBuilder{ builder: selector.GlobalSelector(), }, base.Config{HealthCheck: true}, ) balancer.Register(b) } type balancerBuilder struct { builder selector.Builder } // Build creates a grpc Picker. func (b *balancerBuilder) Build(info base.PickerBuildInfo) balancer.Picker { if len(info.ReadySCs) == 0 { // Block the RPC until a new picker is available via UpdateState(). return base.NewErrPicker(balancer.ErrNoSubConnAvailable) } nodes := make([]selector.Node, 0, len(info.ReadySCs)) for conn, info := range info.ReadySCs { ins, _ := info.Address.Attributes.Value("rawServiceInstance").(*registry.ServiceInstance) nodes = append(nodes, &grpcNode{ Node: selector.NewNode("grpc", info.Address.Addr, ins), subConn: conn, }) } p := &balancerPicker{ selector: b.builder.Build(), } p.selector.Apply(nodes) return p } // balancerPicker is a grpc picker. type balancerPicker struct { selector selector.Selector } // Pick pick instances. func (p *balancerPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { var filters []selector.NodeFilter if tr, ok := transport.FromClientContext(info.Ctx); ok { if gtr, ok := tr.(*Transport); ok { filters = gtr.NodeFilters() } } n, done, err := p.selector.Select(info.Ctx, selector.WithNodeFilter(filters...)) if err != nil { return balancer.PickResult{}, err } return balancer.PickResult{ SubConn: n.(*grpcNode).subConn, Done: func(di balancer.DoneInfo) { done(info.Ctx, selector.DoneInfo{ Err: di.Err, BytesSent: di.BytesSent, BytesReceived: di.BytesReceived, ReplyMD: Trailer(di.Trailer), }) }, }, nil } // Trailer is a grpc trailer MD. type Trailer metadata.MD // Get get a grpc trailer value. func (t Trailer) Get(k string) string { v := metadata.MD(t).Get(k) if len(v) > 0 { return v[0] } return "" } type grpcNode struct { selector.Node subConn balancer.SubConn } ================================================ FILE: transport/grpc/balancer_test.go ================================================ package grpc import ( "context" "reflect" "testing" "google.golang.org/grpc/metadata" "github.com/go-kratos/kratos/v2/selector" ) func TestTrailer(t *testing.T) { trailer := Trailer(metadata.New(map[string]string{"a": "b"})) if !reflect.DeepEqual("b", trailer.Get("a")) { t.Errorf("expect %v, got %v", "b", trailer.Get("a")) } if !reflect.DeepEqual("", trailer.Get("notfound")) { t.Errorf("expect %v, got %v", "", trailer.Get("notfound")) } } func TestFilters(t *testing.T) { o := &clientOptions{} WithNodeFilter(func(_ context.Context, nodes []selector.Node) []selector.Node { return nodes })(o) if !reflect.DeepEqual(1, len(o.filters)) { t.Errorf("expect %v, got %v", 1, len(o.filters)) } } ================================================ FILE: transport/grpc/client.go ================================================ package grpc import ( "context" "crypto/tls" "fmt" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" grpcinsecure "google.golang.org/grpc/credentials/insecure" grpcmd "google.golang.org/grpc/metadata" "github.com/go-kratos/kratos/v2/internal/matcher" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/wrr" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/grpc/resolver/discovery" // init resolver _ "github.com/go-kratos/kratos/v2/transport/grpc/resolver/direct" ) func init() { if selector.GlobalSelector() == nil { selector.SetGlobalSelector(wrr.NewBuilder()) } } // ClientOption is gRPC client option. type ClientOption func(o *clientOptions) // WithEndpoint with client endpoint. func WithEndpoint(endpoint string) ClientOption { return func(o *clientOptions) { o.endpoint = endpoint } } // WithSubset with client discovery subset size. // zero value means subset filter disabled func WithSubset(size int) ClientOption { return func(o *clientOptions) { o.subsetSize = size } } // WithTimeout with client timeout. func WithTimeout(timeout time.Duration) ClientOption { return func(o *clientOptions) { o.timeout = timeout } } // WithMiddleware with client middleware. func WithMiddleware(m ...middleware.Middleware) ClientOption { return func(o *clientOptions) { o.middleware = m } } // WithStreamMiddleware with client stream middleware. func WithStreamMiddleware(m ...middleware.Middleware) ClientOption { return func(o *clientOptions) { o.streamMiddleware = m } } // WithDiscovery with client discovery. func WithDiscovery(d registry.Discovery) ClientOption { return func(o *clientOptions) { o.discovery = d } } // WithTLSConfig with TLS config. func WithTLSConfig(c *tls.Config) ClientOption { return func(o *clientOptions) { o.tlsConf = c } } // WithUnaryInterceptor returns a DialOption that specifies the interceptor for unary RPCs. func WithUnaryInterceptor(in ...grpc.UnaryClientInterceptor) ClientOption { return func(o *clientOptions) { o.ints = in } } // WithStreamInterceptor returns a DialOption that specifies the interceptor for streaming RPCs. func WithStreamInterceptor(in ...grpc.StreamClientInterceptor) ClientOption { return func(o *clientOptions) { o.streamInts = in } } // WithOptions with gRPC options. func WithOptions(opts ...grpc.DialOption) ClientOption { return func(o *clientOptions) { o.grpcOpts = opts } } // WithNodeFilter with select filters func WithNodeFilter(filters ...selector.NodeFilter) ClientOption { return func(o *clientOptions) { o.filters = filters } } // WithHealthCheck with health check func WithHealthCheck(healthCheck bool) ClientOption { return func(o *clientOptions) { if !healthCheck { o.healthCheckConfig = "" } } } // WithLogger with logger // Deprecated: use global logger instead. func WithLogger(log.Logger) ClientOption { return func(*clientOptions) {} } func WithPrintDiscoveryDebugLog(p bool) ClientOption { return func(o *clientOptions) { o.printDiscoveryDebugLog = p } } // clientOptions is gRPC Client type clientOptions struct { endpoint string subsetSize int tlsConf *tls.Config timeout time.Duration discovery registry.Discovery middleware []middleware.Middleware streamMiddleware []middleware.Middleware ints []grpc.UnaryClientInterceptor streamInts []grpc.StreamClientInterceptor grpcOpts []grpc.DialOption balancerName string filters []selector.NodeFilter healthCheckConfig string printDiscoveryDebugLog bool } // Dial returns a GRPC connection. func Dial(ctx context.Context, opts ...ClientOption) (*grpc.ClientConn, error) { return dial(ctx, false, opts...) } // DialInsecure returns an insecure GRPC connection. func DialInsecure(ctx context.Context, opts ...ClientOption) (*grpc.ClientConn, error) { return dial(ctx, true, opts...) } func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.ClientConn, error) { options := clientOptions{ timeout: 2000 * time.Millisecond, balancerName: balancerName, subsetSize: 25, printDiscoveryDebugLog: true, healthCheckConfig: `,"healthCheckConfig":{"serviceName":""}`, } for _, o := range opts { o(&options) } ints := []grpc.UnaryClientInterceptor{ unaryClientInterceptor(options.middleware, options.timeout, options.filters), } sints := []grpc.StreamClientInterceptor{ streamClientInterceptor(options.streamMiddleware, options.filters), } if len(options.ints) > 0 { ints = append(ints, options.ints...) } if len(options.streamInts) > 0 { sints = append(sints, options.streamInts...) } grpcOpts := []grpc.DialOption{ grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]%s}`, options.balancerName, options.healthCheckConfig)), grpc.WithChainUnaryInterceptor(ints...), grpc.WithChainStreamInterceptor(sints...), } if options.discovery != nil { grpcOpts = append(grpcOpts, grpc.WithResolvers( discovery.NewBuilder( options.discovery, discovery.WithInsecure(insecure), discovery.WithTimeout(options.timeout), discovery.WithSubset(options.subsetSize), discovery.PrintDebugLog(options.printDiscoveryDebugLog), ))) } if insecure { grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(grpcinsecure.NewCredentials())) } if options.tlsConf != nil { grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(options.tlsConf))) } if len(options.grpcOpts) > 0 { grpcOpts = append(grpcOpts, options.grpcOpts...) } return grpc.DialContext(ctx, options.endpoint, grpcOpts...) } func unaryClientInterceptor(ms []middleware.Middleware, timeout time.Duration, filters []selector.NodeFilter) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { ctx = transport.NewClientContext(ctx, &Transport{ endpoint: cc.Target(), operation: method, reqHeader: headerCarrier{}, nodeFilters: filters, }) if timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel() } h := func(ctx context.Context, req any) (any, error) { if tr, ok := transport.FromClientContext(ctx); ok { header := tr.RequestHeader() keys := header.Keys() keyvals := make([]string, 0, len(keys)) for _, k := range keys { keyvals = append(keyvals, k, header.Get(k)) } ctx = grpcmd.AppendToOutgoingContext(ctx, keyvals...) } return reply, invoker(ctx, method, req, reply, cc, opts...) } if len(ms) > 0 { h = middleware.Chain(ms...)(h) } var p selector.Peer ctx = selector.NewPeerContext(ctx, &p) _, err := h(ctx, req) return err } } // wrappedClientStream wraps the grpc.ClientStream and applies middleware type wrappedClientStream struct { grpc.ClientStream ctx context.Context middleware matcher.Matcher } func (w *wrappedClientStream) Context() context.Context { return w.ctx } func (w *wrappedClientStream) SendMsg(m any) error { h := func(_ context.Context, req any) (any, error) { return req, w.ClientStream.SendMsg(m) } info, ok := transport.FromClientContext(w.ctx) if !ok { return fmt.Errorf("transport value stored in ctx returns: %v", ok) } if next := w.middleware.Match(info.Operation()); len(next) > 0 { h = middleware.Chain(next...)(h) } _, err := h(w.ctx, m) return err } func (w *wrappedClientStream) RecvMsg(m any) error { h := func(_ context.Context, req any) (any, error) { return req, w.ClientStream.RecvMsg(m) } info, ok := transport.FromClientContext(w.ctx) if !ok { return fmt.Errorf("transport value stored in ctx returns: %v", ok) } if next := w.middleware.Match(info.Operation()); len(next) > 0 { h = middleware.Chain(next...)(h) } _, err := h(w.ctx, m) return err } func streamClientInterceptor(ms []middleware.Middleware, filters []selector.NodeFilter) grpc.StreamClientInterceptor { return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { // nolint ctx = transport.NewClientContext(ctx, &Transport{ endpoint: cc.Target(), operation: method, reqHeader: headerCarrier{}, nodeFilters: filters, }) var p selector.Peer ctx = selector.NewPeerContext(ctx, &p) clientStream, err := streamer(ctx, desc, cc, method, opts...) if err != nil { return nil, err } h := func(_ context.Context, _ any) (any, error) { return streamer, nil } m := matcher.New() if len(ms) > 0 { m.Use(ms...) middleware.Chain(ms...)(h) } wrappedStream := &wrappedClientStream{ ClientStream: clientStream, ctx: ctx, middleware: m, } return wrappedStream, nil } } ================================================ FILE: transport/grpc/client_test.go ================================================ package grpc import ( "context" "crypto/tls" "reflect" "testing" "time" "google.golang.org/grpc" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/registry" ) func TestWithEndpoint(t *testing.T) { o := &clientOptions{} v := "abc" WithEndpoint(v)(o) if !reflect.DeepEqual(v, o.endpoint) { t.Errorf("expect %v but got %v", v, o.endpoint) } } func TestWithTimeout(t *testing.T) { o := &clientOptions{} v := time.Duration(123) WithTimeout(v)(o) if !reflect.DeepEqual(v, o.timeout) { t.Errorf("expect %v but got %v", v, o.timeout) } } func TestWithMiddleware(t *testing.T) { o := &clientOptions{} v := []middleware.Middleware{ func(middleware.Handler) middleware.Handler { return nil }, } WithMiddleware(v...)(o) if !reflect.DeepEqual(v, o.middleware) { t.Errorf("expect %v but got %v", v, o.middleware) } } func TestWithStreamMiddleware(t *testing.T) { o := &clientOptions{} v := []middleware.Middleware{ func(middleware.Handler) middleware.Handler { return nil }, } WithStreamMiddleware(v...)(o) if !reflect.DeepEqual(v, o.streamMiddleware) { t.Errorf("expect %v but got %v", v, o.streamInts) } } type mockRegistry struct{} func (m *mockRegistry) GetService(_ context.Context, _ string) ([]*registry.ServiceInstance, error) { return nil, nil } func (m *mockRegistry) Watch(_ context.Context, _ string) (registry.Watcher, error) { return nil, nil } func TestWithDiscovery(t *testing.T) { o := &clientOptions{} v := &mockRegistry{} WithDiscovery(v)(o) if !reflect.DeepEqual(v, o.discovery) { t.Errorf("expect %v but got %v", v, o.discovery) } } func TestWithTLSConfig(t *testing.T) { o := &clientOptions{} v := &tls.Config{} WithTLSConfig(v)(o) if !reflect.DeepEqual(v, o.tlsConf) { t.Errorf("expect %v but got %v", v, o.tlsConf) } } func EmptyMiddleware() middleware.Middleware { return func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { return handler(ctx, req) } } } func TestUnaryClientInterceptor(t *testing.T) { f := unaryClientInterceptor([]middleware.Middleware{EmptyMiddleware()}, time.Duration(100), nil) req := &struct{}{} resp := &struct{}{} err := f(context.TODO(), "hello", req, resp, &grpc.ClientConn{}, func(context.Context, string, any, any, *grpc.ClientConn, ...grpc.CallOption) error { return nil }) if err != nil { t.Errorf("unexpected error: %v", err) } } func TestWithUnaryInterceptor(t *testing.T) { o := &clientOptions{} v := []grpc.UnaryClientInterceptor{ func(context.Context, string, any, any, *grpc.ClientConn, grpc.UnaryInvoker, ...grpc.CallOption) error { return nil }, func(context.Context, string, any, any, *grpc.ClientConn, grpc.UnaryInvoker, ...grpc.CallOption) error { return nil }, } WithUnaryInterceptor(v...)(o) if !reflect.DeepEqual(v, o.ints) { t.Errorf("expect %v but got %v", v, o.ints) } } func TestWithOptions(t *testing.T) { o := &clientOptions{} v := []grpc.DialOption{ grpc.EmptyDialOption{}, } WithOptions(v...)(o) if !reflect.DeepEqual(v, o.grpcOpts) { t.Errorf("expect %v but got %v", v, o.grpcOpts) } } func TestWithHealthCheck(t *testing.T) { o := &clientOptions{ healthCheckConfig: `,"healthCheckConfig":{"serviceName":""}`, } WithHealthCheck(false)(o) if !reflect.DeepEqual("", o.healthCheckConfig) { t.Errorf("expect %v but got %v", "", o.healthCheckConfig) } } func TestDial(t *testing.T) { o := &clientOptions{} v := []grpc.DialOption{ grpc.EmptyDialOption{}, } WithOptions(v...)(o) if !reflect.DeepEqual(v, o.grpcOpts) { t.Errorf("expect %v but got %v", v, o.grpcOpts) } } func TestDialConn(t *testing.T) { _, err := dial( context.Background(), true, WithDiscovery(&mockRegistry{}), WithTimeout(10*time.Second), WithEndpoint("abc"), WithMiddleware(EmptyMiddleware()), WithStreamMiddleware(EmptyMiddleware()), ) if err != nil { t.Error(err) } } ================================================ FILE: transport/grpc/codec.go ================================================ package grpc import ( "fmt" "google.golang.org/grpc/encoding" "google.golang.org/protobuf/proto" enc "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/encoding/json" ) func init() { encoding.RegisterCodec(codec{}) } // codec is a Codec implementation with protobuf. It is the default codec for gRPC. type codec struct{} func (codec) Marshal(v any) ([]byte, error) { vv, ok := v.(proto.Message) if !ok { return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v) } return enc.GetCodec(json.Name).Marshal(vv) } func (codec) Unmarshal(data []byte, v any) error { vv, ok := v.(proto.Message) if !ok { return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v) } return enc.GetCodec(json.Name).Unmarshal(data, vv) } func (codec) Name() string { return json.Name } ================================================ FILE: transport/grpc/codec_test.go ================================================ package grpc import ( "reflect" "testing" "google.golang.org/protobuf/types/known/structpb" ) func TestCodec(t *testing.T) { in, err := structpb.NewStruct(map[string]any{"Golang": "Kratos"}) if err != nil { t.Errorf("grpc codec create input data error:%v", err) } c := codec{} data, err := c.Marshal(in) if err != nil { t.Errorf("grpc codec marshal error:%v", err) } out := &structpb.Struct{} err = c.Unmarshal(data, out) if err != nil { t.Errorf("grpc codec unmarshal error:%v", err) } if !reflect.DeepEqual(in, out) { t.Errorf("grpc codec want %v, got %v", in, out) } } ================================================ FILE: transport/grpc/interceptor.go ================================================ package grpc import ( "context" "fmt" "google.golang.org/grpc" grpcmd "google.golang.org/grpc/metadata" ic "github.com/go-kratos/kratos/v2/internal/context" "github.com/go-kratos/kratos/v2/internal/matcher" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) // unaryServerInterceptor is a gRPC unary server interceptor func (s *Server) unaryServerInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { ctx, cancel := ic.Merge(ctx, s.baseCtx) defer cancel() md, _ := grpcmd.FromIncomingContext(ctx) replyHeader := grpcmd.MD{} tr := &Transport{ operation: info.FullMethod, reqHeader: headerCarrier(md), replyHeader: headerCarrier(replyHeader), } if s.endpoint != nil { tr.endpoint = s.endpoint.String() } ctx = transport.NewServerContext(ctx, tr) if s.timeout > 0 { ctx, cancel = context.WithTimeout(ctx, s.timeout) defer cancel() } h := func(ctx context.Context, req any) (any, error) { return handler(ctx, req) } if next := s.middleware.Match(tr.Operation()); len(next) > 0 { h = middleware.Chain(next...)(h) } reply, err := h(ctx, req) if len(replyHeader) > 0 { _ = grpc.SetHeader(ctx, replyHeader) } return reply, err } } // wrappedStream is rewrite grpc stream's context type wrappedStream struct { grpc.ServerStream ctx context.Context middleware matcher.Matcher } func NewWrappedStream(ctx context.Context, stream grpc.ServerStream, m matcher.Matcher) grpc.ServerStream { return &wrappedStream{ ServerStream: stream, ctx: ctx, middleware: m, } } func (w *wrappedStream) Context() context.Context { return w.ctx } // streamServerInterceptor is a gRPC stream server interceptor func (s *Server) streamServerInterceptor() grpc.StreamServerInterceptor { return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { ctx, cancel := ic.Merge(ss.Context(), s.baseCtx) defer cancel() md, _ := grpcmd.FromIncomingContext(ctx) replyHeader := grpcmd.MD{} ctx = transport.NewServerContext(ctx, &Transport{ endpoint: s.endpoint.String(), operation: info.FullMethod, reqHeader: headerCarrier(md), replyHeader: headerCarrier(replyHeader), }) h := func(_ context.Context, _ any) (any, error) { return handler(srv, ss), nil } if next := s.streamMiddleware.Match(info.FullMethod); len(next) > 0 { middleware.Chain(next...)(h) } ctx = context.WithValue(ctx, stream{ ServerStream: ss, streamMiddleware: s.streamMiddleware, }, ss) ws := NewWrappedStream(ctx, ss, s.streamMiddleware) err := handler(srv, ws) if len(replyHeader) > 0 { _ = grpc.SetHeader(ctx, replyHeader) } return err } } type stream struct { grpc.ServerStream streamMiddleware matcher.Matcher } func GetStream(ctx context.Context) grpc.ServerStream { return ctx.Value(stream{}).(grpc.ServerStream) } func (w *wrappedStream) SendMsg(m any) error { h := func(_ context.Context, req any) (any, error) { return req, w.ServerStream.SendMsg(m) } info, ok := transport.FromServerContext(w.ctx) if !ok { return fmt.Errorf("transport value stored in ctx returns: %v", ok) } if next := w.middleware.Match(info.Operation()); len(next) > 0 { h = middleware.Chain(next...)(h) } _, err := h(w.ctx, m) return err } func (w *wrappedStream) RecvMsg(m any) error { h := func(_ context.Context, req any) (any, error) { return req, w.ServerStream.RecvMsg(m) } info, ok := transport.FromServerContext(w.ctx) if !ok { return fmt.Errorf("transport value stored in ctx returns: %v", ok) } if next := w.middleware.Match(info.Operation()); len(next) > 0 { h = middleware.Chain(next...)(h) } _, err := h(w.ctx, m) return err } ================================================ FILE: transport/grpc/resolver/direct/builder.go ================================================ package direct import ( "strings" "google.golang.org/grpc/resolver" ) const name = "direct" func init() { resolver.Register(NewBuilder()) } type directBuilder struct{} // NewBuilder creates a directBuilder which is used to factory direct resolvers. // example: // // direct:///127.0.0.1:9000,127.0.0.2:9000 func NewBuilder() resolver.Builder { return &directBuilder{} } func (d *directBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { addrs := make([]resolver.Address, 0) for _, addr := range strings.Split(strings.TrimPrefix(target.URL.Path, "/"), ",") { addrs = append(addrs, resolver.Address{Addr: addr}) } err := cc.UpdateState(resolver.State{ Addresses: addrs, }) if err != nil { return nil, err } return newDirectResolver(), nil } func (d *directBuilder) Scheme() string { return name } ================================================ FILE: transport/grpc/resolver/direct/builder_test.go ================================================ package direct import ( "errors" "reflect" "testing" "google.golang.org/grpc/resolver" "google.golang.org/grpc/serviceconfig" ) func TestDirectBuilder_Scheme(t *testing.T) { b := NewBuilder() if !reflect.DeepEqual(b.Scheme(), "direct") { t.Errorf("expect %v, got %v", "direct", b.Scheme()) } } type mockConn struct { needUpdateStateErr bool } func (m *mockConn) UpdateState(resolver.State) error { if m.needUpdateStateErr { return errors.New("mock test needUpdateStateErr") } return nil } func (m *mockConn) ReportError(error) {} func (m *mockConn) NewAddress(_ []resolver.Address) {} func (m *mockConn) NewServiceConfig(_ string) {} func (m *mockConn) ParseServiceConfig(_ string) *serviceconfig.ParseResult { return nil } func TestDirectBuilder_Build(t *testing.T) { b := NewBuilder() r, err := b.Build(resolver.Target{}, &mockConn{}, resolver.BuildOptions{}) if err != nil { t.Errorf("expect no error, got %v", err) } r.ResolveNow(resolver.ResolveNowOptions{}) r.Close() // need update state err _, err = b.Build(resolver.Target{}, &mockConn{needUpdateStateErr: true}, resolver.BuildOptions{}) if err == nil { t.Errorf("expect needUpdateStateErr, got nil") } } ================================================ FILE: transport/grpc/resolver/direct/resolver.go ================================================ package direct import "google.golang.org/grpc/resolver" type directResolver struct{} func newDirectResolver() resolver.Resolver { return &directResolver{} } func (r *directResolver) Close() { } func (r *directResolver) ResolveNow(_ resolver.ResolveNowOptions) { } ================================================ FILE: transport/grpc/resolver/direct/resolver_test.go ================================================ package direct ================================================ FILE: transport/grpc/resolver/discovery/builder.go ================================================ package discovery import ( "context" "errors" "strings" "time" "github.com/google/uuid" "google.golang.org/grpc/resolver" "github.com/go-kratos/kratos/v2/registry" ) const name = "discovery" var ErrWatcherCreateTimeout = errors.New("discovery create watcher overtime") // Option is builder option. type Option func(o *builder) // WithTimeout with timeout option. func WithTimeout(timeout time.Duration) Option { return func(b *builder) { b.timeout = timeout } } // WithInsecure with isSecure option. func WithInsecure(insecure bool) Option { return func(b *builder) { b.insecure = insecure } } // WithSubset with subset size. func WithSubset(size int) Option { return func(b *builder) { b.subsetSize = size } } // Deprecated: please use PrintDebugLog // DisableDebugLog disables update instances log. func DisableDebugLog() Option { return func(b *builder) { b.debugLog = false } } // PrintDebugLog print grpc resolver watch service log func PrintDebugLog(p bool) Option { return func(b *builder) { b.debugLog = p } } type builder struct { discoverer registry.Discovery timeout time.Duration insecure bool subsetSize int debugLog bool } // NewBuilder creates a builder which is used to factory registry resolvers. func NewBuilder(d registry.Discovery, opts ...Option) resolver.Builder { b := &builder{ discoverer: d, timeout: time.Second * 10, insecure: false, debugLog: true, subsetSize: 25, } for _, o := range opts { o(b) } return b } func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { watchRes := &struct { err error w registry.Watcher }{} done := make(chan struct{}, 1) ctx, cancel := context.WithCancel(context.Background()) go func() { w, err := b.discoverer.Watch(ctx, strings.TrimPrefix(target.URL.Path, "/")) watchRes.w = w watchRes.err = err close(done) }() var err error if b.timeout > 0 { select { case <-done: err = watchRes.err case <-time.After(b.timeout): err = ErrWatcherCreateTimeout } } else { <-done err = watchRes.err } if err != nil { cancel() return nil, err } r := &discoveryResolver{ w: watchRes.w, cc: cc, ctx: ctx, cancel: cancel, insecure: b.insecure, debugLog: b.debugLog, subsetSize: b.subsetSize, selectorKey: uuid.New().String(), } go r.watch() return r, nil } // Scheme return scheme of discovery func (*builder) Scheme() string { return name } ================================================ FILE: transport/grpc/resolver/discovery/builder_test.go ================================================ package discovery import ( "context" "net/url" "reflect" "testing" "time" "google.golang.org/grpc/resolver" "google.golang.org/grpc/serviceconfig" "github.com/go-kratos/kratos/v2/registry" ) func TestWithInsecure(t *testing.T) { b := &builder{} WithInsecure(true)(b) if !b.insecure { t.Errorf("expected insecure to be true") } } func TestWithTimeout(t *testing.T) { o := &builder{} v := time.Duration(123) WithTimeout(v)(o) if !reflect.DeepEqual(v, o.timeout) { t.Errorf("expected %v, got %v", v, o.timeout) } } func TestDisableDebugLog(t *testing.T) { o := &builder{} DisableDebugLog()(o) if o.debugLog { t.Errorf("expected debugLog true, got %v", o.debugLog) } } func TestPrintDebugLog(t *testing.T) { o := &builder{} PrintDebugLog(true)(o) if !o.debugLog { t.Errorf("expected PrintdebugLog true, got %v", o.debugLog) } } type mockDiscovery struct{} func (m *mockDiscovery) GetService(_ context.Context, _ string) ([]*registry.ServiceInstance, error) { return nil, nil } func (m *mockDiscovery) Watch(_ context.Context, _ string) (registry.Watcher, error) { time.Sleep(time.Microsecond * 500) return &testWatch{}, nil } func TestBuilder_Scheme(t *testing.T) { b := NewBuilder(&mockDiscovery{}) if !reflect.DeepEqual("discovery", b.Scheme()) { t.Errorf("expected %v, got %v", "discovery", b.Scheme()) } } type mockConn struct{} func (m *mockConn) UpdateState(resolver.State) error { return nil } func (m *mockConn) ReportError(error) {} func (m *mockConn) NewAddress(_ []resolver.Address) {} func (m *mockConn) NewServiceConfig(_ string) {} func (m *mockConn) ParseServiceConfig(_ string) *serviceconfig.ParseResult { return nil } func TestBuilder_Build(t *testing.T) { b := NewBuilder(&mockDiscovery{}, PrintDebugLog(false)) _, err := b.Build( resolver.Target{ URL: url.URL{ Scheme: resolver.GetDefaultScheme(), Path: "grpc://authority/endpoint", }, }, &mockConn{}, resolver.BuildOptions{}, ) if err != nil { t.Errorf("expected no error, got %v", err) return } timeoutBuilder := NewBuilder(&mockDiscovery{}, WithTimeout(0)) _, err = timeoutBuilder.Build( resolver.Target{ URL: url.URL{ Scheme: resolver.GetDefaultScheme(), Path: "grpc://authority/endpoint", }, }, &mockConn{}, resolver.BuildOptions{}, ) if err != nil { t.Errorf("expected no error, got %v", err) } } ================================================ FILE: transport/grpc/resolver/discovery/resolver.go ================================================ package discovery import ( "context" "encoding/json" "errors" "time" "google.golang.org/grpc/attributes" "google.golang.org/grpc/resolver" "github.com/go-kratos/aegis/subset" "github.com/go-kratos/kratos/v2/internal/endpoint" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" ) type discoveryResolver struct { w registry.Watcher cc resolver.ClientConn ctx context.Context cancel context.CancelFunc insecure bool debugLog bool selectorKey string subsetSize int } func (r *discoveryResolver) watch() { for { select { case <-r.ctx.Done(): return default: } ins, err := r.w.Next() if err != nil { if errors.Is(err, context.Canceled) { return } log.Errorf("[resolver] Failed to watch discovery endpoint: %v", err) time.Sleep(time.Second) continue } r.update(ins) } } func (r *discoveryResolver) update(ins []*registry.ServiceInstance) { var ( endpoints = make(map[string]struct{}) filtered = make([]*registry.ServiceInstance, 0, len(ins)) ) for _, in := range ins { ept, err := endpoint.ParseEndpoint(in.Endpoints, endpoint.Scheme("grpc", !r.insecure)) if err != nil { log.Errorf("[resolver] Failed to parse discovery endpoint: %v", err) continue } if ept == "" { continue } // filter redundant endpoints if _, ok := endpoints[ept]; ok { continue } endpoints[ept] = struct{}{} filtered = append(filtered, in) } if r.subsetSize != 0 { filtered = subset.Subset(r.selectorKey, filtered, r.subsetSize) } addrs := make([]resolver.Address, 0, len(filtered)) for _, in := range filtered { ept, _ := endpoint.ParseEndpoint(in.Endpoints, endpoint.Scheme("grpc", !r.insecure)) addr := resolver.Address{ ServerName: in.Name, Attributes: parseAttributes(in.Metadata).WithValue("rawServiceInstance", in), Addr: ept, } addrs = append(addrs, addr) } if len(addrs) == 0 { log.Warnf("[resolver] Zero endpoint found,refused to write, instances: %v", ins) return } err := r.cc.UpdateState(resolver.State{Addresses: addrs}) if err != nil { log.Errorf("[resolver] failed to update state: %s", err) } if r.debugLog { b, _ := json.Marshal(filtered) log.Infof("[resolver] update instances: %s", b) } } func (r *discoveryResolver) Close() { r.cancel() err := r.w.Stop() if err != nil { log.Errorf("[resolver] failed to watch top: %s", err) } } func (r *discoveryResolver) ResolveNow(_ resolver.ResolveNowOptions) {} func parseAttributes(md map[string]string) (a *attributes.Attributes) { for k, v := range md { a = a.WithValue(k, v) } return a } ================================================ FILE: transport/grpc/resolver/discovery/resolver_test.go ================================================ package discovery import ( "context" "errors" "reflect" "testing" "time" "google.golang.org/grpc/resolver" "github.com/go-kratos/kratos/v2/registry" ) type testClientConn struct { resolver.ClientConn // For unimplemented functions te *testing.T } func (t *testClientConn) UpdateState(s resolver.State) error { t.te.Log("UpdateState", s) return nil } type testWatch struct { err error count uint } func (m *testWatch) Next() ([]*registry.ServiceInstance, error) { time.Sleep(time.Millisecond * 200) if m.count > 1 { return nil, nil } m.count++ ins := []*registry.ServiceInstance{ { ID: "mock_ID", Name: "mock_Name", Version: "mock_Version", Endpoints: []string{"grpc://127.0.0.1?isSecure=true"}, }, { ID: "mock_ID2", Name: "mock_Name2", Version: "mock_Version2", Endpoints: []string{""}, }, } return ins, m.err } // Watch creates a watcher according to the service name. func (m *testWatch) Stop() error { return m.err } func TestWatch(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) r := &discoveryResolver{ w: &testWatch{}, cc: &testClientConn{te: t}, ctx: ctx, cancel: cancel, insecure: false, } r.ResolveNow(resolver.ResolveNowOptions{}) go func() { time.Sleep(time.Second * 2) r.Close() }() r.watch() t.Log("watch goroutine exited after 2 second") } func TestWatchError(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) r := &discoveryResolver{ w: &testWatch{err: errors.New("bad")}, cc: &testClientConn{te: t}, ctx: ctx, cancel: cancel, } go func() { time.Sleep(time.Second * 2) r.Close() }() r.watch() t.Log("watch goroutine exited after 2 second") } func TestWatchContextCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) r := &discoveryResolver{ w: &testWatch{err: context.Canceled}, cc: &testClientConn{te: t}, ctx: ctx, cancel: cancel, } go func() { time.Sleep(time.Second * 2) r.Close() }() r.watch() t.Log("watch goroutine exited after 2 second") } func TestParseAttributes(t *testing.T) { a := parseAttributes(map[string]string{ "a": "b", "c": "d", }) if !reflect.DeepEqual("b", a.Value("a").(string)) { t.Errorf("expect b, got %v", a.Value("a")) } x := a.WithValue("qq", "ww") if !reflect.DeepEqual("ww", x.Value("qq").(string)) { t.Errorf("expect ww, got %v", x.Value("qq")) } if x.Value("notfound") != nil { t.Errorf("expect nil, got %v", x.Value("notfound")) } } ================================================ FILE: transport/grpc/server.go ================================================ package grpc import ( "context" "crypto/tls" "net" "net/url" "time" "google.golang.org/grpc" "google.golang.org/grpc/admin" "google.golang.org/grpc/credentials" "google.golang.org/grpc/health" "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/reflection" apimd "github.com/go-kratos/kratos/v2/api/metadata" "github.com/go-kratos/kratos/v2/internal/endpoint" "github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/matcher" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) var ( _ transport.Server = (*Server)(nil) _ transport.Endpointer = (*Server)(nil) ) // ServerOption is gRPC server option. type ServerOption func(o *Server) // Network with server network. func Network(network string) ServerOption { return func(s *Server) { s.network = network } } // Address with server address. func Address(addr string) ServerOption { return func(s *Server) { s.address = addr } } // Endpoint with server address. func Endpoint(endpoint *url.URL) ServerOption { return func(s *Server) { s.endpoint = endpoint } } // Timeout with server timeout. func Timeout(timeout time.Duration) ServerOption { return func(s *Server) { s.timeout = timeout } } // Logger with server logger. // Deprecated: use global logger instead. func Logger(log.Logger) ServerOption { return func(*Server) {} } // Middleware with server middleware. func Middleware(m ...middleware.Middleware) ServerOption { return func(s *Server) { s.middleware.Use(m...) } } func StreamMiddleware(m ...middleware.Middleware) ServerOption { return func(s *Server) { s.streamMiddleware.Use(m...) } } // CustomHealth Checks server. func CustomHealth() ServerOption { return func(s *Server) { s.customHealth = true } } // TLSConfig with TLS config. func TLSConfig(c *tls.Config) ServerOption { return func(s *Server) { s.tlsConf = c } } // Listener with server lis func Listener(lis net.Listener) ServerOption { return func(s *Server) { s.lis = lis } } // UnaryInterceptor returns a ServerOption that sets the UnaryServerInterceptor for the server. func UnaryInterceptor(in ...grpc.UnaryServerInterceptor) ServerOption { return func(s *Server) { s.unaryInts = in } } // StreamInterceptor returns a ServerOption that sets the StreamServerInterceptor for the server. func StreamInterceptor(in ...grpc.StreamServerInterceptor) ServerOption { return func(s *Server) { s.streamInts = in } } // DisableReflection disable grpc reflection. func DisableReflection() ServerOption { return func(s *Server) { s.disableReflection = true } } // Options with grpc options. func Options(opts ...grpc.ServerOption) ServerOption { return func(s *Server) { s.grpcOpts = opts } } // Server is a gRPC server wrapper. type Server struct { *grpc.Server baseCtx context.Context tlsConf *tls.Config lis net.Listener err error network string address string endpoint *url.URL timeout time.Duration middleware matcher.Matcher streamMiddleware matcher.Matcher unaryInts []grpc.UnaryServerInterceptor streamInts []grpc.StreamServerInterceptor grpcOpts []grpc.ServerOption health *health.Server customHealth bool metadata *apimd.Server adminClean func() disableReflection bool } // NewServer creates a gRPC server by options. func NewServer(opts ...ServerOption) *Server { srv := &Server{ baseCtx: context.Background(), network: "tcp", address: ":0", timeout: 1 * time.Second, health: health.NewServer(), middleware: matcher.New(), streamMiddleware: matcher.New(), } for _, o := range opts { o(srv) } unaryInts := []grpc.UnaryServerInterceptor{ srv.unaryServerInterceptor(), } streamInts := []grpc.StreamServerInterceptor{ srv.streamServerInterceptor(), } if len(srv.unaryInts) > 0 { unaryInts = append(unaryInts, srv.unaryInts...) } if len(srv.streamInts) > 0 { streamInts = append(streamInts, srv.streamInts...) } grpcOpts := []grpc.ServerOption{ grpc.ChainUnaryInterceptor(unaryInts...), grpc.ChainStreamInterceptor(streamInts...), } if srv.tlsConf != nil { grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(srv.tlsConf))) } if len(srv.grpcOpts) > 0 { grpcOpts = append(grpcOpts, srv.grpcOpts...) } srv.Server = grpc.NewServer(grpcOpts...) srv.metadata = apimd.NewServer(srv.Server) // internal register if !srv.customHealth { grpc_health_v1.RegisterHealthServer(srv.Server, srv.health) } apimd.RegisterMetadataServer(srv.Server, srv.metadata) // reflection register if !srv.disableReflection { reflection.Register(srv.Server) } // admin register srv.adminClean, _ = admin.Register(srv.Server) return srv } // Use uses a service middleware with selector. // selector: // - '/*' // - '/helloworld.v1.Greeter/*' // - '/helloworld.v1.Greeter/SayHello' func (s *Server) Use(selector string, m ...middleware.Middleware) { s.middleware.Add(selector, m...) } // Endpoint return a real address to registry endpoint. // examples: // // grpc://127.0.0.1:9000?isSecure=false func (s *Server) Endpoint() (*url.URL, error) { if err := s.listenAndEndpoint(); err != nil { return nil, s.err } return s.endpoint, nil } // Start start the gRPC server. func (s *Server) Start(ctx context.Context) error { if err := s.listenAndEndpoint(); err != nil { return s.err } s.baseCtx = ctx log.Infof("[gRPC] server listening on: %s", s.lis.Addr().String()) s.health.Resume() return s.Serve(s.lis) } // Stop stop the gRPC server. func (s *Server) Stop(ctx context.Context) error { if s.adminClean != nil { s.adminClean() } s.health.Shutdown() done := make(chan struct{}) go func() { defer close(done) log.Info("[gRPC] server stopping") s.GracefulStop() }() select { case <-done: case <-ctx.Done(): log.Warn("[gRPC] server couldn't stop gracefully in time, doing force stop") s.Server.Stop() } return nil } func (s *Server) listenAndEndpoint() error { if s.lis == nil { lis, err := net.Listen(s.network, s.address) if err != nil { s.err = err return err } s.lis = lis } if s.endpoint == nil { addr, err := host.Extract(s.address, s.lis) if err != nil { s.err = err return err } s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("grpc", s.tlsConf != nil), addr) } return s.err } ================================================ FILE: transport/grpc/server_test.go ================================================ package grpc import ( "bytes" "context" "crypto/tls" "fmt" "net" "net/url" "reflect" "strings" "sync" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/internal/matcher" pb "github.com/go-kratos/kratos/v2/internal/testdata/helloworld" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) // server is used to implement helloworld.GreeterServer. type server struct { pb.UnimplementedGreeterServer } func (s *server) SayHelloStream(streamServer pb.Greeter_SayHelloStreamServer) error { tctx, ok := transport.FromServerContext(streamServer.Context()) if ok { tctx.ReplyHeader().Set("123", "123") } var cnt uint for { in, err := streamServer.Recv() if err != nil { return err } if in.Name == "error" { return errors.BadRequest("custom_error", fmt.Sprintf("invalid argument %s", in.Name)) } if in.Name == "panic" { panic("server panic") } err = streamServer.Send(&pb.HelloReply{ Message: fmt.Sprintf("hello %s", in.Name), }) if err != nil { return err } cnt++ if cnt > 1 { return nil } } } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { if in.Name == "error" { return nil, errors.BadRequest("custom_error", fmt.Sprintf("invalid argument %s", in.Name)) } if in.Name == "panic" { panic("server panic") } return &pb.HelloReply{Message: fmt.Sprintf("Hello %+v", in.Name)}, nil } type testKey struct{} func TestServer(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, testKey{}, "test") srv := NewServer( Middleware( func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if tr, ok := transport.FromServerContext(ctx); ok { if tr.ReplyHeader() != nil { tr.ReplyHeader().Set("req_id", "3344") } } return handler(ctx, req) } }), UnaryInterceptor(func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { return handler(ctx, req) }), Options(grpc.InitialConnWindowSize(0)), ) pb.RegisterGreeterServer(srv, &server{}) if e, err := srv.Endpoint(); err != nil || e == nil || strings.HasSuffix(e.Host, ":0") { t.Fatal(e, err) } go func() { // start server if err := srv.Start(ctx); err != nil { panic(err) } }() time.Sleep(time.Second) testClient(t, srv) _ = srv.Stop(ctx) } func testClient(t *testing.T, srv *Server) { u, err := srv.Endpoint() if err != nil { t.Fatal(err) } // new a gRPC client conn, err := DialInsecure(context.Background(), WithEndpoint(u.Host), WithOptions(grpc.WithBlock()), WithUnaryInterceptor( func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { return invoker(ctx, method, req, reply, cc, opts...) }), WithMiddleware(func(handler middleware.Handler) middleware.Handler { return func(ctx context.Context, req any) (reply any, err error) { if tr, ok := transport.FromClientContext(ctx); ok { header := tr.RequestHeader() header.Set("x-md-trace", "2233") } return handler(ctx, req) } }), ) defer func() { _ = conn.Close() }() if err != nil { t.Fatal(err) } client := pb.NewGreeterClient(conn) reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "kratos"}) t.Log(err) if err != nil { t.Errorf("failed to call: %v", err) } if !reflect.DeepEqual(reply.Message, "Hello kratos") { t.Errorf("expect %s, got %s", "Hello kratos", reply.Message) } streamCli, err := client.SayHelloStream(context.Background()) if err != nil { t.Error(err) return } defer func() { _ = streamCli.CloseSend() }() err = streamCli.Send(&pb.HelloRequest{Name: "cc"}) if err != nil { t.Error(err) return } reply, err = streamCli.Recv() if err != nil { t.Error(err) return } if !reflect.DeepEqual(reply.Message, "hello cc") { t.Errorf("expect %s, got %s", "hello cc", reply.Message) } } func TestNetwork(t *testing.T) { o := &Server{} v := "abc" Network(v)(o) if !reflect.DeepEqual(v, o.network) { t.Errorf("expect %s, got %s", v, o.network) } } func TestAddress(t *testing.T) { v := "abc" o := NewServer(Address(v)) if !reflect.DeepEqual(v, o.address) { t.Errorf("expect %s, got %s", v, o.address) } u, err := o.Endpoint() if err == nil { t.Errorf("expect %s, got %s", v, err) } if u != nil { t.Errorf("expect %s, got %s", v, u) } } func TestTimeout(t *testing.T) { o := &Server{} v := time.Duration(123) Timeout(v)(o) if !reflect.DeepEqual(v, o.timeout) { t.Errorf("expect %s, got %s", v, o.timeout) } } func TestTLSConfig(t *testing.T) { o := &Server{} v := &tls.Config{} TLSConfig(v)(o) if !reflect.DeepEqual(v, o.tlsConf) { t.Errorf("expect %v, got %v", v, o.tlsConf) } } func TestUnaryInterceptor(t *testing.T) { o := &Server{} v := []grpc.UnaryServerInterceptor{ func(context.Context, any, *grpc.UnaryServerInfo, grpc.UnaryHandler) (resp any, err error) { return nil, nil }, func(context.Context, any, *grpc.UnaryServerInfo, grpc.UnaryHandler) (resp any, err error) { return nil, nil }, } UnaryInterceptor(v...)(o) if !reflect.DeepEqual(v, o.unaryInts) { t.Errorf("expect %v, got %v", v, o.unaryInts) } } func TestStreamInterceptor(t *testing.T) { o := &Server{} v := []grpc.StreamServerInterceptor{ func(any, grpc.ServerStream, *grpc.StreamServerInfo, grpc.StreamHandler) error { return nil }, func(any, grpc.ServerStream, *grpc.StreamServerInfo, grpc.StreamHandler) error { return nil }, } StreamInterceptor(v...)(o) if !reflect.DeepEqual(v, o.streamInts) { t.Errorf("expect %v, got %v", v, o.streamInts) } } func TestOptions(t *testing.T) { o := &Server{} v := []grpc.ServerOption{ grpc.EmptyServerOption{}, } Options(v...)(o) if !reflect.DeepEqual(v, o.grpcOpts) { t.Errorf("expect %v, got %v", v, o.grpcOpts) } } type testResp struct { Data string } func TestServer_unaryServerInterceptor(t *testing.T) { u, err := url.Parse("grpc://hello/world") if err != nil { t.Errorf("expect %v, got %v", nil, err) } srv := &Server{ baseCtx: context.Background(), endpoint: u, timeout: time.Duration(10), middleware: matcher.New(), } srv.middleware.Use(EmptyMiddleware()) req := &struct{}{} rv, err := srv.unaryServerInterceptor()(context.TODO(), req, &grpc.UnaryServerInfo{}, func(context.Context, any) (any, error) { return &testResp{Data: "hi"}, nil }) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual("hi", rv.(*testResp).Data) { t.Errorf("expect %s, got %s", "hi", rv.(*testResp).Data) } } type mockServerStream struct { ctx context.Context sentMsg any recvMsg any metadata metadata.MD grpc.ServerStream } func (m *mockServerStream) SetHeader(md metadata.MD) error { m.metadata = md return nil } func (m *mockServerStream) SendHeader(md metadata.MD) error { m.metadata = md return nil } func (m *mockServerStream) SetTrailer(md metadata.MD) { m.metadata = md } func (m *mockServerStream) Context() context.Context { return m.ctx } func (m *mockServerStream) SendMsg(msg any) error { m.sentMsg = msg return nil } func (m *mockServerStream) RecvMsg(msg any) error { m.recvMsg = msg return nil } func TestServer_streamServerInterceptor(t *testing.T) { u, err := url.Parse("grpc://hello/world") if err != nil { t.Errorf("expect %v, got %v", nil, err) } srv := &Server{ baseCtx: context.Background(), endpoint: u, timeout: time.Duration(10), middleware: matcher.New(), streamMiddleware: matcher.New(), } srv.streamMiddleware.Use(EmptyMiddleware()) mockStream := &mockServerStream{ ctx: srv.baseCtx, } handler := func(_ any, stream grpc.ServerStream) error { resp := &testResp{Data: "stream hi"} return stream.SendMsg(resp) } info := &grpc.StreamServerInfo{ FullMethod: "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo", } err = srv.streamServerInterceptor()(nil, mockStream, info, handler) if err != nil { t.Errorf("expect %v, got %v", nil, err) } // Check response resp := mockStream.sentMsg.(*testResp) if !reflect.DeepEqual("stream hi", resp.Data) { t.Errorf("expect %s, got %s", "stream hi", resp.Data) } } func TestListener(t *testing.T) { lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) } s := &Server{} Listener(lis)(s) if !reflect.DeepEqual(lis, s.lis) { t.Errorf("expect %v, got %v", lis, s.lis) } if e, err := s.Endpoint(); err != nil || e == nil { t.Errorf("expect not empty") } } func TestStop(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() tests := []struct { name string ctx context.Context cancel context.CancelFunc wantForceStop bool }{ { name: "normal", ctx: context.Background(), cancel: func() {}, wantForceStop: false, }, { name: "timeout", ctx: timeoutCtx, cancel: cancel, wantForceStop: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { l, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) } defer l.Close() old := log.GetLogger() defer log.SetLogger(old) // Create a logger to capture logs var logs safeBytesBuffer log.SetLogger(log.NewStdLogger(&logs)) s := NewServer(Listener(l)) pb.RegisterGreeterServer(s, &server{}) go func() { err := s.Start(context.Background()) //nolint if err != nil { log.Fatal(err) } }() time.Sleep(100 * time.Millisecond) conn, err := DialInsecure( context.Background(), WithEndpoint(l.Addr().String()), WithOptions(grpc.WithBlock()), ) if err != nil { t.Fatal(err) } defer conn.Close() go func() { client := pb.NewGreeterClient(conn) if tt.wantForceStop { // Simulate a long-running request s, err := client.SayHelloStream(context.Background()) //nolint if err != nil { log.Fatal(err) } // Keep the stream open for { // Intentionally do not send messages, only receive messages _, err := s.Recv() if err != nil { break } } } else { _, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "test"}) //nolint if err != nil { log.Error(err) } } }() time.Sleep(100 * time.Millisecond) err = s.Stop(tt.ctx) if err != nil { t.Errorf("Expected no error, got %v", err) return } // Check if the stop was forced or graceful if tt.wantForceStop { if !strings.Contains(logs.String(), "force stop") { t.Errorf("Expected force stop\n%s", logs.String()) } } else { if strings.Contains(logs.String(), "force stop") { t.Errorf("Expected graceful stop\n%s", logs.String()) } } }) } } type safeBytesBuffer struct { mu sync.Mutex buf bytes.Buffer } func (b *safeBytesBuffer) Write(p []byte) (n int, err error) { b.mu.Lock() defer b.mu.Unlock() return b.buf.Write(p) } func (b *safeBytesBuffer) String() string { b.mu.Lock() defer b.mu.Unlock() return b.buf.String() } ================================================ FILE: transport/grpc/transport.go ================================================ package grpc import ( "google.golang.org/grpc/metadata" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/transport" ) var _ transport.Transporter = (*Transport)(nil) // Transport is a gRPC transport. type Transport struct { endpoint string operation string reqHeader headerCarrier replyHeader headerCarrier nodeFilters []selector.NodeFilter } // Kind returns the transport kind. func (tr *Transport) Kind() transport.Kind { return transport.KindGRPC } // Endpoint returns the transport endpoint. func (tr *Transport) Endpoint() string { return tr.endpoint } // Operation returns the transport operation. func (tr *Transport) Operation() string { return tr.operation } // RequestHeader returns the request header. func (tr *Transport) RequestHeader() transport.Header { return tr.reqHeader } // ReplyHeader returns the reply header. func (tr *Transport) ReplyHeader() transport.Header { return tr.replyHeader } // NodeFilters returns the client select filters. func (tr *Transport) NodeFilters() []selector.NodeFilter { return tr.nodeFilters } type headerCarrier metadata.MD // Get returns the value associated with the passed key. func (mc headerCarrier) Get(key string) string { vals := metadata.MD(mc).Get(key) if len(vals) > 0 { return vals[0] } return "" } // Set stores the key-value pair. func (mc headerCarrier) Set(key string, value string) { metadata.MD(mc).Set(key, value) } // Add append value to key-values pair. func (mc headerCarrier) Add(key string, value string) { metadata.MD(mc).Append(key, value) } // Keys lists the keys stored in this carrier. func (mc headerCarrier) Keys() []string { keys := make([]string, 0, len(mc)) for k := range metadata.MD(mc) { keys = append(keys, k) } return keys } // Values returns a slice of values associated with the passed key. func (mc headerCarrier) Values(key string) []string { return metadata.MD(mc).Get(key) } ================================================ FILE: transport/grpc/transport_test.go ================================================ package grpc import ( "reflect" "sort" "testing" "github.com/go-kratos/kratos/v2/transport" ) func TestTransport_Kind(t *testing.T) { o := &Transport{} if !reflect.DeepEqual(transport.KindGRPC, o.Kind()) { t.Errorf("expect %v, got %v", transport.KindGRPC, o.Kind()) } } func TestTransport_Endpoint(t *testing.T) { v := "hello" o := &Transport{endpoint: v} if !reflect.DeepEqual(v, o.Endpoint()) { t.Errorf("expect %v, got %v", v, o.Endpoint()) } } func TestTransport_Operation(t *testing.T) { v := "hello" o := &Transport{operation: v} if !reflect.DeepEqual(v, o.Operation()) { t.Errorf("expect %v, got %v", v, o.Operation()) } } func TestTransport_RequestHeader(t *testing.T) { v := headerCarrier{} v.Set("a", "1") o := &Transport{reqHeader: v} if !reflect.DeepEqual("1", o.RequestHeader().Get("a")) { t.Errorf("expect %v, got %v", "1", o.RequestHeader().Get("a")) } if !reflect.DeepEqual("", o.RequestHeader().Get("notfound")) { t.Errorf("expect %v, got %v", "", o.RequestHeader().Get("notfound")) } } func TestTransport_ReplyHeader(t *testing.T) { v := headerCarrier{} v.Set("a", "1") o := &Transport{replyHeader: v} if !reflect.DeepEqual("1", o.ReplyHeader().Get("a")) { t.Errorf("expect %v, got %v", "1", o.ReplyHeader().Get("a")) } } func TestHeaderCarrier_Keys(t *testing.T) { v := headerCarrier{} v.Set("abb", "1") v.Set("bcc", "2") want := []string{"abb", "bcc"} keys := v.Keys() sort.Slice(want, func(i, j int) bool { return want[i] < want[j] }) sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) if !reflect.DeepEqual(want, keys) { t.Errorf("expect %v, got %v", want, keys) } } ================================================ FILE: transport/http/binding/bind.go ================================================ package binding import ( "net/http" "net/url" "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/encoding/form" "github.com/go-kratos/kratos/v2/errors" ) // BindQuery bind vars parameters to target. func BindQuery(vars url.Values, target any) error { if err := encoding.GetCodec(form.Name).Unmarshal([]byte(vars.Encode()), target); err != nil { return errors.BadRequest("CODEC", err.Error()) } return nil } // BindForm bind form parameters to target. func BindForm(req *http.Request, target any) error { if err := req.ParseForm(); err != nil { return err } if err := encoding.GetCodec(form.Name).Unmarshal([]byte(req.Form.Encode()), target); err != nil { return errors.BadRequest("CODEC", err.Error()) } return nil } ================================================ FILE: transport/http/binding/bind_test.go ================================================ package binding import ( "errors" "io" "net/http" "net/url" "reflect" "strings" "testing" kratoserror "github.com/go-kratos/kratos/v2/errors" ) type ( TestBind struct { Name string `json:"name"` URL string `json:"url"` } TestBind2 struct { Age int `json:"age"` } ) func TestBindQuery(t *testing.T) { type args struct { vars url.Values target any } tests := []struct { name string args args err error want any }{ { name: "test", args: args{ vars: map[string][]string{"name": {"kratos"}, "url": {"https://go-kratos.dev/"}}, target: &TestBind{}, }, err: nil, want: &TestBind{"kratos", "https://go-kratos.dev/"}, }, { name: "test1", args: args{ vars: map[string][]string{"age": {"kratos"}, "url": {"https://go-kratos.dev/"}}, target: &TestBind2{}, }, err: kratoserror.BadRequest("CODEC", "Field Namespace:age ERROR:Invalid Integer Value 'kratos' Type 'int' Namespace 'age'"), }, { name: "test2", args: args{ vars: map[string][]string{"age": {"1"}, "url": {"https://go-kratos.dev/"}}, target: &TestBind2{}, }, err: nil, want: &TestBind2{Age: 1}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := BindQuery(tt.args.vars, tt.args.target) if !kratoserror.Is(err, tt.err) { t.Fatalf("BindQuery() error = %v, err %v", err, tt.err) } if err == nil && !reflect.DeepEqual(tt.args.target, tt.want) { t.Errorf("BindQuery() target = %v, want %v", tt.args.target, tt.want) } }) } } func TestBindForm(t *testing.T) { type args struct { req *http.Request target any } tests := []struct { name string args args err error want *TestBind }{ { name: "error not nil", args: args{ req: &http.Request{Method: http.MethodPost}, target: &TestBind{}, }, err: errors.New("missing form body"), want: nil, }, { name: "error is nil", args: args{ req: &http.Request{ Method: http.MethodPost, Header: http.Header{"Content-Type": {"application/x-www-form-urlencoded; param=value"}}, Body: io.NopCloser(strings.NewReader("name=kratos&url=https://go-kratos.dev/")), }, target: &TestBind{}, }, err: nil, want: &TestBind{"kratos", "https://go-kratos.dev/"}, }, { name: "error BadRequest", args: args{ req: &http.Request{ Method: http.MethodPost, Header: http.Header{"Content-Type": {"application/x-www-form-urlencoded; param=value"}}, Body: io.NopCloser(strings.NewReader("age=a")), }, target: &TestBind2{}, }, err: kratoserror.BadRequest("CODEC", "Field Namespace:age ERROR:Invalid Integer Value 'a' Type 'int' Namespace 'age'"), want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := BindForm(tt.args.req, tt.args.target) if !reflect.DeepEqual(err, tt.err) { t.Fatalf("BindForm() error = %v, err %v", err, tt.err) } if err == nil && !reflect.DeepEqual(tt.args.target, tt.want) { t.Errorf("BindForm() target = %v, want %v", tt.args.target, tt.want) } }) } } ================================================ FILE: transport/http/binding/encode.go ================================================ package binding import ( "reflect" "regexp" "strings" "google.golang.org/protobuf/proto" "github.com/go-kratos/kratos/v2/encoding/form" ) var reg = regexp.MustCompile(`{[\\.\w]+}`) // EncodeURL encode proto message to url path. func EncodeURL(pathTemplate string, msg any, needQuery bool) string { if msg == nil || (reflect.ValueOf(msg).Kind() == reflect.Ptr && reflect.ValueOf(msg).IsNil()) { return pathTemplate } queryParams, _ := form.EncodeValues(msg) pathParams := make(map[string]struct{}) var path string if strings.ContainsRune(pathTemplate, '{') { path = reg.ReplaceAllStringFunc(pathTemplate, func(in string) string { // it's unreachable because the reg means that must have more than one char in {} // if len(in) < 4 { //nolint:mnd // ** explain the 4 number here :-) ** // return in // } key := in[1 : len(in)-1] pathParams[key] = struct{}{} return queryParams.Get(key) }) } else { path = pathTemplate } if !needQuery { if v, ok := msg.(proto.Message); ok { if query := form.EncodeFieldMask(v.ProtoReflect()); query != "" { return path + "?" + query } } return path } if len(queryParams) > 0 { for key := range pathParams { delete(queryParams, key) } if query := queryParams.Encode(); query != "" { path += "?" + query } } return path } ================================================ FILE: transport/http/binding/encode_test.go ================================================ package binding import ( "testing" "google.golang.org/protobuf/types/known/fieldmaskpb" "github.com/go-kratos/kratos/v2/internal/testdata/binding" ) func TestEncodeURL(t *testing.T) { tests := []struct { pathTemplate string request *binding.HelloRequest needQuery bool want string }{ { pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.naming}", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "2233!!!!"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/test/sub/2233!!!!", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.naming}", request: nil, needQuery: false, want: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.naming}", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{}/sub/{sub.naming}", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "hello"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/{}/sub/hello", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{}/sub/{sub.name.cc}", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "hello"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/{}/sub/", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{}/sub/{test_repeated}", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "hello"}, TestRepeated: []string{"123", "456"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/{}/sub/123", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.naming}", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "5566!!!"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/test/sub/5566!!!", }, { pathTemplate: "http://helloworld.Greeter/helloworld/sub", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "2233!!!"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/sub", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.name}", request: &binding.HelloRequest{Name: "test"}, needQuery: false, want: "http://helloworld.Greeter/helloworld/test/sub/", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub", request: &binding.HelloRequest{Name: "go", Sub: &binding.Sub{Name: "kratos"}}, needQuery: true, want: "http://helloworld.Greeter/helloworld/go/sub?sub.naming=kratos", }, { pathTemplate: "http://helloworld.Greeter/helloworld/sub/{sub.naming}", request: &binding.HelloRequest{Sub: &binding.Sub{Name: "kratos"}, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "sub.naming"}}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/sub/kratos?updateMask=name,sub.naming", }, { pathTemplate: "http://helloworld.Greeter/helloworld/sub/[{sub.naming}]", request: &binding.HelloRequest{Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/sub/[kratos]", }, { pathTemplate: "http://helloworld.Greeter/helloworld/[{name}]/sub/[{sub.naming}]", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/[test]/sub/[kratos]", }, { pathTemplate: "http://helloworld.Greeter/helloworld/[{}]/sub/[{sub.naming}]", request: &binding.HelloRequest{Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/[{}]/sub/[kratos]", }, { pathTemplate: "http://helloworld.Greeter/helloworld/[{}]/sub/[{sub.naming}]/{[]}", request: &binding.HelloRequest{Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/[{}]/sub/[kratos]/{[]}", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{[sub]}/[{sub.naming}]", request: &binding.HelloRequest{Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/{[sub]}/[kratos]", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{[name]}/[{sub.naming}]", request: &binding.HelloRequest{Name: "test", Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/{[name]}/[kratos]", }, { pathTemplate: "http://helloworld.Greeter/helloworld/{}/[]/[{sub.naming}]", request: &binding.HelloRequest{Sub: &binding.Sub{Name: "kratos"}}, needQuery: false, want: "http://helloworld.Greeter/helloworld/{}/[]/[kratos]", }, } for _, test := range tests { if path := EncodeURL(test.pathTemplate, test.request, test.needQuery); path != test.want { t.Fatalf("want: %s, got: %s", test.want, path) } } } func BenchmarkEncodeURL(b *testing.B) { benchmarks := []struct { name string pathTemplate string msg *binding.HelloRequest needQuery bool }{ { name: "NoParams", pathTemplate: "http://helloworld.Greeter/helloworld/sub", msg: &binding.HelloRequest{ Name: "test", Sub: &binding.Sub{Name: "kratos"}, }, needQuery: false, }, { name: "NoParamsWithQuery", pathTemplate: "http://helloworld.Greeter/helloworld/sub", msg: &binding.HelloRequest{ Name: "test", Sub: &binding.Sub{Name: "kratos"}, UpdateMask: &fieldmaskpb.FieldMask{ Paths: []string{"name", "sub.naming"}, }, }, needQuery: true, }, { name: "WithParams", pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.naming}", msg: &binding.HelloRequest{ Name: "test", Sub: &binding.Sub{Name: "kratos"}, }, needQuery: false, }, { name: "WithParamsAndQuery", pathTemplate: "http://helloworld.Greeter/helloworld/{name}/sub/{sub.naming}", msg: &binding.HelloRequest{ Name: "test", Sub: &binding.Sub{Name: "kratos"}, UpdateMask: &fieldmaskpb.FieldMask{ Paths: []string{"name", "sub.naming"}, }, }, needQuery: true, }, } for _, bm := range benchmarks { b.Run(bm.name, func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { EncodeURL(bm.pathTemplate, bm.msg, bm.needQuery) } }) } } ================================================ FILE: transport/http/calloption.go ================================================ package http import ( "net/http" ) // CallOption configures a Call before it starts or extracts information from // a Call after it completes. type CallOption interface { // before is called before the call is sent to any server. If before // returns a non-nil error, the RPC fails with that error. before(*callInfo) error // after is called after the call has completed. after cannot return an // error, so any failures should be reported via output parameters. after(*callInfo, *csAttempt) } type callInfo struct { contentType string operation string pathTemplate string headerCarrier *http.Header } // EmptyCallOption does not alter the Call configuration. // It can be embedded in another structure to carry satellite data for use // by interceptors. type EmptyCallOption struct{} func (EmptyCallOption) before(*callInfo) error { return nil } func (EmptyCallOption) after(*callInfo, *csAttempt) {} type csAttempt struct { res *http.Response } // ContentType with request content type. func ContentType(contentType string) CallOption { return ContentTypeCallOption{ContentType: contentType} } // ContentTypeCallOption is BodyCallOption type ContentTypeCallOption struct { EmptyCallOption ContentType string } func (o ContentTypeCallOption) before(c *callInfo) error { c.contentType = o.ContentType return nil } func defaultCallInfo(path string) callInfo { return callInfo{ contentType: "application/json", operation: path, pathTemplate: path, } } // Operation is serviceMethod call option func Operation(operation string) CallOption { return OperationCallOption{Operation: operation} } // OperationCallOption is set ServiceMethod for client call type OperationCallOption struct { EmptyCallOption Operation string } func (o OperationCallOption) before(c *callInfo) error { c.operation = o.Operation return nil } // PathTemplate is http path template func PathTemplate(pattern string) CallOption { return PathTemplateCallOption{Pattern: pattern} } // PathTemplateCallOption is set path template for client call type PathTemplateCallOption struct { EmptyCallOption Pattern string } func (o PathTemplateCallOption) before(c *callInfo) error { c.pathTemplate = o.Pattern return nil } // Header returns a CallOptions that retrieves the http response header // from server reply. func Header(header *http.Header) CallOption { return HeaderCallOption{header: header} } // HeaderCallOption is retrieve response header for client call type HeaderCallOption struct { EmptyCallOption header *http.Header } func (o HeaderCallOption) before(c *callInfo) error { c.headerCarrier = o.header return nil } func (o HeaderCallOption) after(_ *callInfo, cs *csAttempt) { if cs.res != nil && cs.res.Header != nil { *o.header = cs.res.Header } } ================================================ FILE: transport/http/calloption_test.go ================================================ package http import ( "net/http" "reflect" "testing" ) func TestEmptyCallOptions(t *testing.T) { e := EmptyCallOption{} if e.before(&callInfo{}) != nil { t.Error("EmptyCallOption should be ignored") } e.after(&callInfo{}, &csAttempt{}) } func TestContentType(t *testing.T) { if !reflect.DeepEqual(ContentType("aaa").(ContentTypeCallOption).ContentType, "aaa") { t.Errorf("want: %v,got: %v", "aaa", ContentType("aaa").(ContentTypeCallOption).ContentType) } } func TestContentTypeCallOption_before(t *testing.T) { c := &callInfo{} err := ContentType("aaa").before(c) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual("aaa", c.contentType) { t.Errorf("want: %v, got: %v", "aaa", c.contentType) } } func TestDefaultCallInfo(t *testing.T) { path := "hi" rv := defaultCallInfo(path) if !reflect.DeepEqual(path, rv.pathTemplate) { t.Errorf("expect %v, got %v", path, rv.pathTemplate) } if !reflect.DeepEqual(path, rv.operation) { t.Errorf("expect %v, got %v", path, rv.operation) } if !reflect.DeepEqual("application/json", rv.contentType) { t.Errorf("expect %v, got %v", "application/json", rv.contentType) } } func TestOperation(t *testing.T) { if !reflect.DeepEqual("aaa", Operation("aaa").(OperationCallOption).Operation) { t.Errorf("want: %v,got: %v", "aaa", Operation("aaa").(OperationCallOption).Operation) } } func TestOperationCallOption_before(t *testing.T) { c := &callInfo{} err := Operation("aaa").before(c) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual("aaa", c.operation) { t.Errorf("want: %v, got: %v", "aaa", c.operation) } } func TestPathTemplate(t *testing.T) { if !reflect.DeepEqual("aaa", PathTemplate("aaa").(PathTemplateCallOption).Pattern) { t.Errorf("want: %v,got: %v", "aaa", PathTemplate("aaa").(PathTemplateCallOption).Pattern) } } func TestPathTemplateCallOption_before(t *testing.T) { c := &callInfo{} err := PathTemplate("aaa").before(c) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual("aaa", c.pathTemplate) { t.Errorf("want: %v, got: %v", "aaa", c.pathTemplate) } } func TestHeader(t *testing.T) { h := http.Header{"A": []string{"123"}} if !reflect.DeepEqual(Header(&h).(HeaderCallOption).header.Get("A"), "123") { t.Errorf("want: %v,got: %v", "123", Header(&h).(HeaderCallOption).header.Get("A")) } } func TestHeaderCallOption_before(t *testing.T) { h := http.Header{"A": []string{"123"}} c := &callInfo{} o := Header(&h) err := o.before(c) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(&h, c.headerCarrier) { t.Errorf("want: %v,got: %v", &h, o.(HeaderCallOption).header) } } func TestHeaderCallOption_after(t *testing.T) { h := http.Header{"A": []string{"123"}} c := &callInfo{} cs := &csAttempt{res: &http.Response{Header: h}} o := Header(&h) o.after(c, cs) if !reflect.DeepEqual(&h, o.(HeaderCallOption).header) { t.Errorf("want: %v,got: %v", &h, o.(HeaderCallOption).header) } } ================================================ FILE: transport/http/client.go ================================================ package http import ( "bytes" "context" "crypto/tls" "fmt" "io" "net/http" "time" "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/httputil" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" "github.com/go-kratos/kratos/v2/selector/wrr" "github.com/go-kratos/kratos/v2/transport" ) func init() { if selector.GlobalSelector() == nil { selector.SetGlobalSelector(wrr.NewBuilder()) } } // DecodeErrorFunc is decode error func. type DecodeErrorFunc func(ctx context.Context, res *http.Response) error // EncodeRequestFunc is request encode func. type EncodeRequestFunc func(ctx context.Context, contentType string, in any) (body []byte, err error) // DecodeResponseFunc is response decode func. type DecodeResponseFunc func(ctx context.Context, res *http.Response, out any) error // ClientOption is HTTP client option. type ClientOption func(*clientOptions) // Client is an HTTP transport client. type clientOptions struct { ctx context.Context tlsConf *tls.Config timeout time.Duration endpoint string userAgent string encoder EncodeRequestFunc decoder DecodeResponseFunc errorDecoder DecodeErrorFunc transport http.RoundTripper nodeFilters []selector.NodeFilter discovery registry.Discovery middleware []middleware.Middleware block bool subsetSize int } // WithSubset with client discovery subset size. // zero value means subset filter disabled func WithSubset(size int) ClientOption { return func(o *clientOptions) { o.subsetSize = size } } // WithTransport with client transport. func WithTransport(trans http.RoundTripper) ClientOption { return func(o *clientOptions) { o.transport = trans } } // WithTimeout with client request timeout. func WithTimeout(d time.Duration) ClientOption { return func(o *clientOptions) { o.timeout = d } } // WithUserAgent with client user agent. func WithUserAgent(ua string) ClientOption { return func(o *clientOptions) { o.userAgent = ua } } // WithMiddleware with client middleware. func WithMiddleware(m ...middleware.Middleware) ClientOption { return func(o *clientOptions) { o.middleware = m } } // WithEndpoint with client addr. func WithEndpoint(endpoint string) ClientOption { return func(o *clientOptions) { o.endpoint = endpoint } } // WithRequestEncoder with client request encoder. func WithRequestEncoder(encoder EncodeRequestFunc) ClientOption { return func(o *clientOptions) { o.encoder = encoder } } // WithResponseDecoder with client response decoder. func WithResponseDecoder(decoder DecodeResponseFunc) ClientOption { return func(o *clientOptions) { o.decoder = decoder } } // WithErrorDecoder with client error decoder. func WithErrorDecoder(errorDecoder DecodeErrorFunc) ClientOption { return func(o *clientOptions) { o.errorDecoder = errorDecoder } } // WithDiscovery with client discovery. func WithDiscovery(d registry.Discovery) ClientOption { return func(o *clientOptions) { o.discovery = d } } // WithNodeFilter with select filters func WithNodeFilter(filters ...selector.NodeFilter) ClientOption { return func(o *clientOptions) { o.nodeFilters = filters } } // WithBlock with client block. func WithBlock() ClientOption { return func(o *clientOptions) { o.block = true } } // WithTLSConfig with tls config. func WithTLSConfig(c *tls.Config) ClientOption { return func(o *clientOptions) { o.tlsConf = c } } // Client is an HTTP client. type Client struct { opts clientOptions target *Target r *resolver cc *http.Client insecure bool selector selector.Selector } // NewClient returns an HTTP client. func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) { options := clientOptions{ ctx: ctx, timeout: 2000 * time.Millisecond, encoder: DefaultRequestEncoder, decoder: DefaultResponseDecoder, errorDecoder: DefaultErrorDecoder, transport: http.DefaultTransport, subsetSize: 25, } for _, o := range opts { o(&options) } if options.tlsConf != nil { if tr, ok := options.transport.(*http.Transport); ok { tr.TLSClientConfig = options.tlsConf } } insecure := options.tlsConf == nil target, err := parseTarget(options.endpoint, insecure) if err != nil { return nil, err } selector := selector.GlobalSelector().Build() var r *resolver if options.discovery != nil { if target.Scheme == "discovery" { if r, err = newResolver(ctx, options.discovery, target, selector, options.block, insecure, options.subsetSize); err != nil { return nil, fmt.Errorf("[http client] new resolver failed for endpoint %q: %w", options.endpoint, err) } } else if _, _, err := host.ExtractHostPort(options.endpoint); err != nil { return nil, fmt.Errorf("[http client] invalid endpoint format %q: %w", options.endpoint, err) } } return &Client{ opts: options, target: target, insecure: insecure, r: r, cc: &http.Client{ Timeout: options.timeout, Transport: options.transport, }, selector: selector, }, nil } // Invoke makes a rpc call procedure for remote service. func (client *Client) Invoke(ctx context.Context, method, path string, args any, reply any, opts ...CallOption) error { var ( contentType string body io.Reader ) c := defaultCallInfo(path) for _, o := range opts { if err := o.before(&c); err != nil { return err } } if args != nil { data, err := client.opts.encoder(ctx, c.contentType, args) if err != nil { return err } contentType = c.contentType body = bytes.NewReader(data) } url := fmt.Sprintf("%s://%s%s", client.target.Scheme, client.target.Authority, path) req, err := http.NewRequest(method, url, body) if err != nil { return err } if c.headerCarrier != nil { req.Header = *c.headerCarrier } if contentType != "" { req.Header.Set("Content-Type", c.contentType) } if client.opts.userAgent != "" { req.Header.Set("User-Agent", client.opts.userAgent) } ctx = transport.NewClientContext(ctx, &Transport{ endpoint: client.opts.endpoint, reqHeader: headerCarrier(req.Header), operation: c.operation, request: req, pathTemplate: c.pathTemplate, }) return client.invoke(ctx, req, args, reply, c, opts...) } func (client *Client) invoke(ctx context.Context, req *http.Request, args any, reply any, c callInfo, opts ...CallOption) error { h := func(ctx context.Context, _ any) (any, error) { res, err := client.do(req.WithContext(ctx)) if res != nil { cs := csAttempt{res: res} for _, o := range opts { o.after(&c, &cs) } } if err != nil { return nil, err } defer res.Body.Close() if err := client.opts.decoder(ctx, res, reply); err != nil { return nil, err } return reply, nil } var p selector.Peer ctx = selector.NewPeerContext(ctx, &p) if len(client.opts.middleware) > 0 { h = middleware.Chain(client.opts.middleware...)(h) } _, err := h(ctx, args) return err } // Do send an HTTP request and decodes the body of response into target. // returns an error (of type *Error) if the response status code is not 2xx. func (client *Client) Do(req *http.Request, opts ...CallOption) (*http.Response, error) { c := defaultCallInfo(req.URL.Path) for _, o := range opts { if err := o.before(&c); err != nil { return nil, err } } return client.do(req) } func (client *Client) do(req *http.Request) (*http.Response, error) { var done func(context.Context, selector.DoneInfo) if client.r != nil { var ( err error node selector.Node ) if node, done, err = client.selector.Select(req.Context(), selector.WithNodeFilter(client.opts.nodeFilters...)); err != nil { return nil, errors.ServiceUnavailable("NODE_NOT_FOUND", err.Error()) } if client.insecure { req.URL.Scheme = "http" } else { req.URL.Scheme = "https" } req.URL.Host = node.Address() req.Host = node.Address() } resp, err := client.cc.Do(req) if err == nil { t, ok := transport.FromClientContext(req.Context()) if ok { ht, ok := t.(*Transport) if ok { ht.replyHeader = headerCarrier(resp.Header) } } err = client.opts.errorDecoder(req.Context(), resp) } if done != nil { done(req.Context(), selector.DoneInfo{Err: err}) } if err != nil { return nil, err } return resp, nil } // Close tears down the Transport and all underlying connections. func (client *Client) Close() error { if client.r != nil { return client.r.Close() } return nil } // DefaultRequestEncoder is an HTTP request encoder. func DefaultRequestEncoder(_ context.Context, contentType string, in any) ([]byte, error) { name := httputil.ContentSubtype(contentType) body, err := encoding.GetCodec(name).Marshal(in) if err != nil { return nil, err } return body, err } // DefaultResponseDecoder is an HTTP response decoder. func DefaultResponseDecoder(_ context.Context, res *http.Response, v any) error { defer res.Body.Close() data, err := io.ReadAll(res.Body) if err != nil { return err } return CodecForResponse(res).Unmarshal(data, v) } // DefaultErrorDecoder is an HTTP error decoder. func DefaultErrorDecoder(_ context.Context, res *http.Response) error { if res.StatusCode >= 200 && res.StatusCode <= 299 { return nil } defer res.Body.Close() data, err := io.ReadAll(res.Body) if err == nil { e := new(errors.Error) if err = CodecForResponse(res).Unmarshal(data, e); err == nil { e.Code = int32(res.StatusCode) return e } } return errors.Newf(res.StatusCode, errors.UnknownReason, "").WithCause(err) } // CodecForResponse get encoding.Codec via http.Response func CodecForResponse(r *http.Response) encoding.Codec { codec := encoding.GetCodec(httputil.ContentSubtype(r.Header.Get("Content-Type"))) if codec != nil { return codec } return encoding.GetCodec("json") } ================================================ FILE: transport/http/client_test.go ================================================ package http import ( "bytes" "context" "crypto/tls" "encoding/json" "errors" "fmt" "io" "log" "net/http" "reflect" "strconv" "testing" "time" kratoserrors "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) type mockRoundTripper struct{} func (rt *mockRoundTripper) RoundTrip(_ *http.Request) (resp *http.Response, err error) { return } type mockCallOption struct { needErr bool } func (x *mockCallOption) before(_ *callInfo) error { if x.needErr { return errors.New("option need return err") } return nil } func (x *mockCallOption) after(_ *callInfo, _ *csAttempt) { log.Println("run in mockCallOption.after") } func TestWithSubset(t *testing.T) { co := &clientOptions{} o := WithSubset(1) o(co) if co.subsetSize != 1 { t.Error("expected subset size to be 1") } } func TestWithTransport(t *testing.T) { ov := &mockRoundTripper{} o := WithTransport(ov) co := &clientOptions{} o(co) if !reflect.DeepEqual(co.transport, ov) { t.Errorf("expected transport to be %v, got %v", ov, co.transport) } } func TestWithTimeout(t *testing.T) { ov := 1 * time.Second o := WithTimeout(ov) co := &clientOptions{} o(co) if !reflect.DeepEqual(co.timeout, ov) { t.Errorf("expected timeout to be %v, got %v", ov, co.timeout) } } func TestWithBlock(t *testing.T) { o := WithBlock() co := &clientOptions{} o(co) if !co.block { t.Errorf("expected block to be true, got %v", co.block) } } func TestWithTLSConfig(t *testing.T) { ov := &tls.Config{} o := WithTLSConfig(ov) co := &clientOptions{} o(co) if !reflect.DeepEqual(co.tlsConf, ov) { t.Errorf("expected tls config to be %v, got %v", ov, co.tlsConf) } } func TestWithUserAgent(t *testing.T) { ov := "kratos" o := WithUserAgent(ov) co := &clientOptions{} o(co) if !reflect.DeepEqual(co.userAgent, ov) { t.Errorf("expected user agent to be %v, got %v", ov, co.userAgent) } } func TestWithMiddleware(t *testing.T) { o := &clientOptions{} v := []middleware.Middleware{ func(middleware.Handler) middleware.Handler { return nil }, } WithMiddleware(v...)(o) if !reflect.DeepEqual(o.middleware, v) { t.Errorf("expected middleware to be %v, got %v", v, o.middleware) } } func TestWithEndpoint(t *testing.T) { ov := "some-endpoint" o := WithEndpoint(ov) co := &clientOptions{} o(co) if !reflect.DeepEqual(co.endpoint, ov) { t.Errorf("expected endpoint to be %v, got %v", ov, co.endpoint) } } func TestWithRequestEncoder(t *testing.T) { o := &clientOptions{} v := func(context.Context, string, any) (body []byte, err error) { return nil, nil } WithRequestEncoder(v)(o) if o.encoder == nil { t.Errorf("expected encoder to be not nil") } } func TestWithResponseDecoder(t *testing.T) { o := &clientOptions{} v := func(context.Context, *http.Response, any) error { return nil } WithResponseDecoder(v)(o) if o.decoder == nil { t.Errorf("expected encoder to be not nil") } } func TestWithErrorDecoder(t *testing.T) { o := &clientOptions{} v := func(context.Context, *http.Response) error { return nil } WithErrorDecoder(v)(o) if o.errorDecoder == nil { t.Errorf("expected encoder to be not nil") } } type mockDiscovery struct{} func (*mockDiscovery) GetService(_ context.Context, _ string) ([]*registry.ServiceInstance, error) { return nil, nil } func (*mockDiscovery) Watch(_ context.Context, _ string) (registry.Watcher, error) { return &mockWatcher{}, nil } type mockWatcher struct{} func (m *mockWatcher) Next() ([]*registry.ServiceInstance, error) { instance := ®istry.ServiceInstance{ ID: "1", Name: "kratos", Version: "v1", Metadata: map[string]string{}, Endpoints: []string{fmt.Sprintf("http://127.0.0.1:9001?isSecure=%s", strconv.FormatBool(false))}, } time.Sleep(time.Millisecond * 500) return []*registry.ServiceInstance{instance}, nil } func (*mockWatcher) Stop() error { return nil } func TestWithDiscovery(t *testing.T) { ov := &mockDiscovery{} o := WithDiscovery(ov) co := &clientOptions{} o(co) if !reflect.DeepEqual(co.discovery, ov) { t.Errorf("expected discovery to be %v, got %v", ov, co.discovery) } } func TestWithNodeFilter(t *testing.T) { ov := func(context.Context, []selector.Node) []selector.Node { return []selector.Node{&selector.DefaultNode{}} } o := WithNodeFilter(ov) co := &clientOptions{} o(co) for _, n := range co.nodeFilters { ret := n(context.Background(), nil) if len(ret) != 1 { t.Errorf("expected node length to be 1, got %v", len(ret)) } } } func TestDefaultRequestEncoder(t *testing.T) { r, _ := http.NewRequest(http.MethodPost, "", io.NopCloser(bytes.NewBufferString(`{"a":"1", "b": 2}`))) r.Header.Set("Content-Type", "application/xml") v1 := &struct { A string `json:"a"` B int64 `json:"b"` }{"a", 1} b, err := DefaultRequestEncoder(context.TODO(), "application/json", v1) if err != nil { t.Fatal(err) } v1b := &struct { A string `json:"a"` B int64 `json:"b"` }{} err = json.Unmarshal(b, v1b) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(v1b, v1) { t.Errorf("expected %v, got %v", v1, v1b) } } func TestDefaultResponseDecoder(t *testing.T) { resp1 := &http.Response{ Header: make(http.Header), StatusCode: 200, Body: io.NopCloser(bytes.NewBufferString(`{"a":"1", "b": 2}`)), } v1 := &struct { A string `json:"a"` B int64 `json:"b"` }{} err := DefaultResponseDecoder(context.TODO(), resp1, &v1) if err != nil { t.Fatal(err) } if v1.A != "1" { t.Errorf("expected %v, got %v", "1", v1.A) } if v1.B != int64(2) { t.Errorf("expected %v, got %v", 2, v1.B) } resp2 := &http.Response{ Header: make(http.Header), StatusCode: 200, Body: io.NopCloser(bytes.NewBufferString("{badjson}")), } v2 := &struct { A string `json:"a"` B int64 `json:"b"` }{} err = DefaultResponseDecoder(context.TODO(), resp2, &v2) syntaxErr := &json.SyntaxError{} if !errors.As(err, &syntaxErr) { t.Errorf("expected %v, got %v", syntaxErr, err) } } func TestDefaultErrorDecoder(t *testing.T) { for i := 200; i < 300; i++ { resp := &http.Response{Header: make(http.Header), StatusCode: i} if DefaultErrorDecoder(context.TODO(), resp) != nil { t.Errorf("expected no error, got %v", DefaultErrorDecoder(context.TODO(), resp)) } } resp1 := &http.Response{ Header: make(http.Header), StatusCode: 300, Body: io.NopCloser(bytes.NewBufferString("{\"foo\":\"bar\"}")), } if DefaultErrorDecoder(context.TODO(), resp1) == nil { t.Errorf("expected error, got nil") } resp2 := &http.Response{ Header: make(http.Header), StatusCode: 500, Body: io.NopCloser(bytes.NewBufferString(`{"code":54321, "message": "hi", "reason": "FOO"}`)), } err := DefaultErrorDecoder(context.TODO(), resp2) if err == nil { t.Errorf("expected error, got nil") } if err.(*kratoserrors.Error).Code != int32(500) { t.Errorf("expected %v, got %v", 500, err.(*kratoserrors.Error).Code) } if err.(*kratoserrors.Error).Message != "hi" { t.Errorf("expected %v, got %v", "hi", err.(*kratoserrors.Error).Message) } if err.(*kratoserrors.Error).Reason != "FOO" { t.Errorf("expected %v, got %v", "FOO", err.(*kratoserrors.Error).Reason) } } func TestCodecForResponse(t *testing.T) { resp := &http.Response{Header: make(http.Header)} resp.Header.Set("Content-Type", "application/xml") c := CodecForResponse(resp) if !reflect.DeepEqual("xml", c.Name()) { t.Errorf("expected %v, got %v", "xml", c.Name()) } } func TestNewClient(t *testing.T) { _, err := NewClient(context.Background(), WithEndpoint("127.0.0.1:8888")) if err != nil { t.Error(err) } _, err = NewClient(context.Background(), WithEndpoint("127.0.0.1:9999"), WithTLSConfig(&tls.Config{ServerName: "www.kratos.com", RootCAs: nil})) if err != nil { t.Error(err) } _, err = NewClient(context.Background(), WithDiscovery(&mockDiscovery{}), WithEndpoint("discovery:///go-kratos")) if err != nil { t.Error(err) } _, err = NewClient(context.Background(), WithDiscovery(&mockDiscovery{}), WithEndpoint("127.0.0.1:8888")) if err != nil { t.Error(err) } _, err = NewClient(context.Background(), WithEndpoint("127.0.0.1:8888:xxxxa")) if err == nil { t.Error("except a parseTarget error") } _, err = NewClient(context.Background(), WithDiscovery(&mockDiscovery{}), WithEndpoint("https://go-kratos.dev/")) if err == nil { t.Error("err should not be equal to nil") } client, err := NewClient( context.Background(), WithDiscovery(&mockDiscovery{}), WithEndpoint("discovery:///go-kratos"), WithMiddleware(func(handler middleware.Handler) middleware.Handler { t.Logf("handle in middleware") return func(ctx context.Context, req any) (any, error) { return handler(ctx, req) } }), ) if err != nil { t.Fatal(err) } err = client.Invoke(context.Background(), http.MethodPost, "/go", map[string]string{"name": "kratos"}, nil, EmptyCallOption{}, &mockCallOption{}) if err == nil { t.Error("err should not be equal to nil") } err = client.Invoke(context.Background(), http.MethodPost, "/go", map[string]string{"name": "kratos"}, nil, EmptyCallOption{}, &mockCallOption{needErr: true}) if err == nil { t.Error("err should be equal to callOption err") } client.opts.encoder = func(context.Context, string, any) (body []byte, err error) { return nil, errors.New("mock test encoder error") } err = client.Invoke(context.Background(), http.MethodPost, "/go", map[string]string{"name": "kratos"}, nil, EmptyCallOption{}) if err == nil { t.Error("err should be equal to encoder error") } } ================================================ FILE: transport/http/codec.go ================================================ package http import ( "bytes" "fmt" "io" "net/http" "net/url" "github.com/gorilla/mux" "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/internal/httputil" "github.com/go-kratos/kratos/v2/transport/http/binding" ) // SupportPackageIsVersion1 These constants should not be referenced from any other code. const SupportPackageIsVersion1 = true // Redirector replies to the request with a redirect to url // which may be a path relative to the request path. type Redirector interface { error Redirect() (string, int) } // Request type net/http. type Request = http.Request // ResponseWriter type net/http. type ResponseWriter = http.ResponseWriter // Flusher type net/http type Flusher = http.Flusher // DecodeRequestFunc is decode request func. type DecodeRequestFunc func(*http.Request, any) error // EncodeResponseFunc is encode response func. type EncodeResponseFunc func(http.ResponseWriter, *http.Request, any) error // EncodeErrorFunc is encode error func. type EncodeErrorFunc func(http.ResponseWriter, *http.Request, error) // DefaultRequestVars decodes the request vars to object. func DefaultRequestVars(r *http.Request, v any) error { raws := mux.Vars(r) vars := make(url.Values, len(raws)) for k, v := range raws { vars[k] = []string{v} } return binding.BindQuery(vars, v) } // DefaultRequestQuery decodes the request vars to object. func DefaultRequestQuery(r *http.Request, v any) error { return binding.BindQuery(r.URL.Query(), v) } // DefaultRequestDecoder decodes the request body to object. func DefaultRequestDecoder(r *http.Request, v any) error { codec, ok := CodecForRequest(r, "Content-Type") if !ok { return errors.BadRequest("CODEC", fmt.Sprintf("unregister Content-Type: %s", r.Header.Get("Content-Type"))) } data, err := io.ReadAll(r.Body) // reset body. r.Body = io.NopCloser(bytes.NewBuffer(data)) if err != nil { return errors.BadRequest("CODEC", err.Error()) } if len(data) == 0 { return nil } if err = codec.Unmarshal(data, v); err != nil { return errors.BadRequest("CODEC", fmt.Sprintf("body unmarshal %s", err.Error())) } return nil } // DefaultResponseEncoder encodes the object to the HTTP response. func DefaultResponseEncoder(w http.ResponseWriter, r *http.Request, v any) error { if v == nil { return nil } if rd, ok := v.(Redirector); ok { url, code := rd.Redirect() http.Redirect(w, r, url, code) return nil } codec, _ := CodecForRequest(r, "Accept") data, err := codec.Marshal(v) if err != nil { return err } w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) _, err = w.Write(data) if err != nil { return err } return nil } // DefaultErrorEncoder encodes the error to the HTTP response. func DefaultErrorEncoder(w http.ResponseWriter, r *http.Request, err error) { var rd *redirect if errors.As(err, &rd) { url, code := rd.Redirect() http.Redirect(w, r, url, code) return } se := errors.FromError(err) codec, _ := CodecForRequest(r, "Accept") body, err := codec.Marshal(se) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", httputil.ContentType(codec.Name())) w.WriteHeader(int(se.Code)) _, _ = w.Write(body) } // CodecForRequest get encoding.Codec via http.Request func CodecForRequest(r *http.Request, name string) (encoding.Codec, bool) { for _, accept := range r.Header[name] { codec := encoding.GetCodec(httputil.ContentSubtype(accept)) if codec != nil { return codec, true } } return encoding.GetCodec("json"), false } ================================================ FILE: transport/http/codec_go1.20.go ================================================ //go:build go1.20 package http import "net/http" // ResponseController is type net/http.ResponseController which was added in Go 1.20. type ResponseController = http.ResponseController ================================================ FILE: transport/http/codec_test.go ================================================ package http import ( "bytes" "io" "net/http" "testing" "github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/errors" ) func TestDefaultRequestDecoder(t *testing.T) { var ( bodyStr = `{"a":"1", "b": 2}` r, _ = http.NewRequest(http.MethodPost, "", io.NopCloser(bytes.NewBufferString(bodyStr))) ) r.Header.Set("Content-Type", "application/json") v1 := &struct { A string `json:"a"` B int64 `json:"b"` }{} err := DefaultRequestDecoder(r, &v1) if err != nil { t.Fatal(err) } if v1.A != "1" { t.Errorf("expected %v, got %v", "1", v1.A) } if v1.B != int64(2) { t.Errorf("expected %v, got %v", 2, v1.B) } data, err := io.ReadAll(r.Body) if err != nil { t.Fatal(err) } if bodyStr != string(data) { t.Errorf("expected %v, got %v", bodyStr, string(data)) } } type mockResponseWriter struct { StatusCode int Data []byte header http.Header } func (w *mockResponseWriter) Header() http.Header { return w.header } func (w *mockResponseWriter) Write(b []byte) (int, error) { w.Data = b return len(b), nil } func (w *mockResponseWriter) WriteHeader(statusCode int) { w.StatusCode = statusCode } type errorCodec struct{} func (errorCodec) Marshal(any) ([]byte, error) { return nil, errors.New(500, "mock", "marshal error") } func (errorCodec) Unmarshal([]byte, any) error { return nil } func (errorCodec) Name() string { return "mock" } func TestDefaultResponseEncoder(t *testing.T) { var ( w = &mockResponseWriter{StatusCode: 200, header: make(http.Header)} r, _ = http.NewRequest(http.MethodPost, "", nil) v = &struct { A string `json:"a"` B int64 `json:"b"` }{ A: "1", B: 2, } ) r.Header.Set("Content-Type", "application/json") err := DefaultResponseEncoder(w, r, v) if err != nil { t.Fatal(err) } if w.Header().Get("Content-Type") != "application/json" { t.Errorf("expected %v, got %v", "application/json", w.Header().Get("Content-Type")) } if w.StatusCode != 200 { t.Errorf("expected %v, got %v", 200, w.StatusCode) } if w.Data == nil { t.Errorf("expected not nil, got %v", w.Data) } } func TestDefaultErrorEncoder(t *testing.T) { var ( w = &mockResponseWriter{header: make(http.Header)} r, _ = http.NewRequest(http.MethodPost, "", nil) err = errors.New(511, "", "") ) r.Header.Set("Content-Type", "application/json") DefaultErrorEncoder(w, r, err) if w.Header().Get("Content-Type") != "application/json" { t.Errorf("expected %v, got %v", "application/json", w.Header().Get("Content-Type")) } if w.StatusCode != 511 { t.Errorf("expected %v, got %v", 511, w.StatusCode) } if w.Data == nil { t.Errorf("expected not nil, got %v", w.Data) } } func TestDefaultErrorEncoderRedirect(t *testing.T) { w := &mockResponseWriter{header: make(http.Header)} r, _ := http.NewRequest(http.MethodGet, "/test", nil) DefaultErrorEncoder(w, r, NewRedirect("/redirect", http.StatusTemporaryRedirect)) if w.StatusCode != http.StatusTemporaryRedirect { t.Errorf("expected %v, got %v", http.StatusTemporaryRedirect, w.StatusCode) } if w.Header().Get("Location") != "/redirect" { t.Errorf("expected %v, got %v", "/redirect", w.Header().Get("Location")) } } func TestDefaultErrorEncoderMarshalError(t *testing.T) { encoding.RegisterCodec(errorCodec{}) w := &mockResponseWriter{header: make(http.Header)} r, _ := http.NewRequest(http.MethodGet, "", nil) r.Header.Set("Accept", "application/mock") DefaultErrorEncoder(w, r, errors.New(500, "mock", "marshal error")) if w.StatusCode != http.StatusInternalServerError { t.Errorf("expected %v, got %v", http.StatusInternalServerError, w.StatusCode) } if w.Header().Get("Content-Type") != "" { t.Errorf("expected empty content type, got %v", w.Header().Get("Content-Type")) } if w.Data != nil { t.Errorf("expected nil, got %v", w.Data) } } func TestDefaultResponseEncoderEncodeNil(t *testing.T) { var ( w = &mockResponseWriter{StatusCode: 204, header: make(http.Header)} r, _ = http.NewRequest(http.MethodPost, "", io.NopCloser(bytes.NewBufferString(""))) ) r.Header.Set("Content-Type", "application/json") err := DefaultResponseEncoder(w, r, nil) if err != nil { t.Fatal(err) } if w.Header().Get("Content-Type") != "" { t.Errorf("expected empty string, got %v", w.Header().Get("Content-Type")) } if w.StatusCode != 204 { t.Errorf("expected %v, got %v", 204, w.StatusCode) } if w.Data != nil { t.Errorf("expected nil, got %v", w.Data) } } func TestCodecForRequest(t *testing.T) { r, _ := http.NewRequest(http.MethodPost, "", io.NopCloser(bytes.NewBufferString(""))) r.Header.Set("Content-Type", "application/xml") c, ok := CodecForRequest(r, "Content-Type") if !ok { t.Fatalf("expected true, got %v", ok) } if c.Name() != "xml" { t.Errorf("expected %v, got %v", "xml", c.Name()) } r, _ = http.NewRequest(http.MethodPost, "", io.NopCloser(bytes.NewBufferString(`{"a":"1", "b": 2}`))) r.Header.Set("Content-Type", "blablablabla") c, ok = CodecForRequest(r, "Content-Type") if ok { t.Fatalf("expected false, got %v", ok) } if c.Name() != "json" { t.Errorf("expected %v, got %v", "json", c.Name()) } } ================================================ FILE: transport/http/context.go ================================================ package http import ( "context" "encoding/json" "encoding/xml" "io" "net/http" "net/url" "time" "github.com/gorilla/mux" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport/http/binding" ) var _ Context = (*wrapper)(nil) // Context is an HTTP Context. type Context interface { context.Context Vars() url.Values Query() url.Values Form() url.Values Header() http.Header Request() *http.Request Response() http.ResponseWriter Middleware(middleware.Handler) middleware.Handler Bind(any) error BindVars(any) error BindQuery(any) error BindForm(any) error Returns(any, error) error Result(int, any) error JSON(int, any) error XML(int, any) error String(int, string) error Blob(int, string, []byte) error Stream(int, string, io.Reader) error Reset(http.ResponseWriter, *http.Request) } type responseWriter struct { code int w http.ResponseWriter } func (w *responseWriter) reset(res http.ResponseWriter) { w.w = res w.code = http.StatusOK } func (w *responseWriter) Header() http.Header { return w.w.Header() } func (w *responseWriter) WriteHeader(statusCode int) { w.code = statusCode } func (w *responseWriter) Write(data []byte) (int, error) { w.w.WriteHeader(w.code) return w.w.Write(data) } func (w *responseWriter) Unwrap() http.ResponseWriter { return w.w } type wrapper struct { router *Router req *http.Request res http.ResponseWriter w responseWriter } func (c *wrapper) Header() http.Header { return c.req.Header } func (c *wrapper) Vars() url.Values { raws := mux.Vars(c.req) vars := make(url.Values, len(raws)) for k, v := range raws { vars[k] = []string{v} } return vars } func (c *wrapper) Form() url.Values { if err := c.req.ParseForm(); err != nil { return url.Values{} } return c.req.Form } func (c *wrapper) Query() url.Values { return c.req.URL.Query() } func (c *wrapper) Request() *http.Request { return c.req } func (c *wrapper) Response() http.ResponseWriter { return c.res } func (c *wrapper) Middleware(h middleware.Handler) middleware.Handler { if tr, ok := transport.FromServerContext(c.req.Context()); ok { return middleware.Chain(c.router.srv.middleware.Match(tr.Operation())...)(h) } return middleware.Chain(c.router.srv.middleware.Match(c.req.URL.Path)...)(h) } func (c *wrapper) Bind(v any) error { return c.router.srv.decBody(c.req, v) } func (c *wrapper) BindVars(v any) error { return c.router.srv.decVars(c.req, v) } func (c *wrapper) BindQuery(v any) error { return c.router.srv.decQuery(c.req, v) } func (c *wrapper) BindForm(v any) error { return binding.BindForm(c.req, v) } func (c *wrapper) Returns(v any, err error) error { if err != nil { return err } return c.router.srv.enc(&c.w, c.req, v) } func (c *wrapper) Result(code int, v any) error { c.w.WriteHeader(code) return c.router.srv.enc(&c.w, c.req, v) } func (c *wrapper) JSON(code int, v any) error { c.res.Header().Set("Content-Type", "application/json") c.res.WriteHeader(code) return json.NewEncoder(c.res).Encode(v) } func (c *wrapper) XML(code int, v any) error { c.res.Header().Set("Content-Type", "application/xml") c.res.WriteHeader(code) return xml.NewEncoder(c.res).Encode(v) } func (c *wrapper) String(code int, text string) error { c.res.Header().Set("Content-Type", "text/plain") c.res.WriteHeader(code) _, err := c.res.Write([]byte(text)) if err != nil { return err } return nil } func (c *wrapper) Blob(code int, contentType string, data []byte) error { c.res.Header().Set("Content-Type", contentType) c.res.WriteHeader(code) _, err := c.res.Write(data) if err != nil { return err } return nil } func (c *wrapper) Stream(code int, contentType string, rd io.Reader) error { c.res.Header().Set("Content-Type", contentType) c.res.WriteHeader(code) _, err := io.Copy(c.res, rd) return err } func (c *wrapper) Reset(res http.ResponseWriter, req *http.Request) { c.w.reset(res) c.res = res c.req = req } func (c *wrapper) Deadline() (time.Time, bool) { if c.req == nil { return time.Time{}, false } return c.req.Context().Deadline() } func (c *wrapper) Done() <-chan struct{} { if c.req == nil { return nil } return c.req.Context().Done() } func (c *wrapper) Err() error { if c.req == nil { return context.Canceled } return c.req.Context().Err() } func (c *wrapper) Value(key any) any { if c.req == nil { return nil } return c.req.Context().Value(key) } ================================================ FILE: transport/http/context_test.go ================================================ package http import ( "bytes" "context" "errors" "net/http" "net/http/httptest" "net/url" "reflect" "testing" "time" ) var testRouter = &Router{srv: NewServer()} func TestContextHeader(t *testing.T) { w := wrapper{ router: testRouter, req: &http.Request{Header: map[string][]string{"name": {"kratos"}}}, res: nil, w: responseWriter{}, } h := w.Header() if !reflect.DeepEqual(h, http.Header{"name": {"kratos"}}) { t.Errorf("expected %v, got %v", http.Header{"name": {"kratos"}}, h) } } func TestContextForm(t *testing.T) { w := wrapper{ router: testRouter, req: &http.Request{Header: map[string][]string{"name": {"kratos"}}, Method: http.MethodPost}, res: nil, w: responseWriter{}, } form := w.Form() if !reflect.DeepEqual(form, url.Values{}) { t.Errorf("expected %v, got %v", url.Values{}, form) } w = wrapper{ router: testRouter, req: &http.Request{Form: map[string][]string{"name": {"kratos"}}}, res: nil, w: responseWriter{}, } form = w.Form() if !reflect.DeepEqual(form, url.Values{"name": {"kratos"}}) { t.Errorf("expected %v, got %v", url.Values{"name": {"kratos"}}, form) } } func TestContextQuery(t *testing.T) { w := wrapper{ router: testRouter, req: &http.Request{URL: &url.URL{Scheme: "https", Host: "github.com", Path: "go-kratos/kratos", RawQuery: "page=1"}, Method: http.MethodPost}, res: nil, w: responseWriter{}, } q := w.Query() if !reflect.DeepEqual(q, url.Values{"page": {"1"}}) { t.Errorf("expected %v, got %v", url.Values{"page": {"1"}}, q) } } func TestContextRequest(t *testing.T) { req := &http.Request{Method: http.MethodPost} w := wrapper{ router: testRouter, req: req, res: nil, w: responseWriter{}, } res := w.Request() if !reflect.DeepEqual(res, req) { t.Errorf("expected %v, got %v", req, res) } } func TestContextResponse(t *testing.T) { res := httptest.NewRecorder() w := wrapper{ router: &Router{srv: &Server{enc: DefaultResponseEncoder}}, req: &http.Request{Method: http.MethodPost}, res: res, w: responseWriter{200, res}, } if !reflect.DeepEqual(w.Response(), res) { t.Errorf("expected %v, got %v", res, w.Response()) } err := w.Returns(map[string]string{}, nil) if err != nil { t.Errorf("expected %v, got %v", nil, err) } needErr := errors.New("some error") err = w.Returns(map[string]string{}, needErr) if !errors.Is(err, needErr) { t.Errorf("expected %v, got %v", needErr, err) } } func TestResponseUnwrap(t *testing.T) { res := httptest.NewRecorder() f := func(rw http.ResponseWriter, _ *http.Request, _ any) error { u, ok := rw.(interface { Unwrap() http.ResponseWriter }) if !ok { return errors.New("can not unwrap") } w := u.Unwrap() if !reflect.DeepEqual(w, res) { return errors.New("underlying response writer not equal") } return nil } w := wrapper{ router: &Router{srv: &Server{enc: f}}, req: nil, res: res, w: responseWriter{200, res}, } err := w.Result(200, "ok") if err != nil { t.Errorf("expected %v, got %v", nil, err) } } func TestContextBindQuery(t *testing.T) { w := wrapper{ router: testRouter, req: &http.Request{URL: &url.URL{Scheme: "https", Host: "go-kratos-dev", RawQuery: "page=2"}}, res: nil, w: responseWriter{}, } type BindQuery struct { Page int `json:"page"` } b := BindQuery{} err := w.BindQuery(&b) if err != nil { t.Errorf("expected %v, got %v", nil, err) } if !reflect.DeepEqual(b, BindQuery{Page: 2}) { t.Errorf("expected %v, got %v", BindQuery{Page: 2}, b) } } func TestContextBindForm(t *testing.T) { w := wrapper{ router: testRouter, req: &http.Request{URL: &url.URL{Scheme: "https", Host: "go-kratos-dev"}, Form: map[string][]string{"page": {"2"}}}, res: nil, w: responseWriter{}, } type BindForm struct { Page int `json:"page"` } b := BindForm{} err := w.BindForm(&b) if err != nil { t.Errorf("expected %v, got %v", nil, err) } if !reflect.DeepEqual(b, BindForm{Page: 2}) { t.Errorf("expected %v, got %v", BindForm{Page: 2}, b) } } func TestContextResponseReturn(t *testing.T) { writer := httptest.NewRecorder() w := wrapper{ router: testRouter, req: nil, res: writer, w: responseWriter{}, } err := w.JSON(200, "success") if err != nil { t.Errorf("expected %v, got %v", nil, err) } err = w.XML(200, "success") if err != nil { t.Errorf("expected %v, got %v", nil, err) } err = w.String(200, "success") if err != nil { t.Errorf("expected %v, got %v", nil, err) } err = w.Blob(200, "blob", []byte("success")) if err != nil { t.Errorf("expected %v, got %v", nil, err) } err = w.Stream(200, "stream", bytes.NewBuffer([]byte("success"))) if err != nil { t.Errorf("expected %v, got %v", nil, err) } } func TestContextCtx(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() req := &http.Request{Method: http.MethodPost} req = req.WithContext(ctx) w := wrapper{ router: testRouter, req: req, res: nil, w: responseWriter{}, } _, ok := w.Deadline() if !ok { t.Errorf("expected %v, got %v", true, ok) } done := w.Done() if done == nil { t.Errorf("expected %v, got %v", true, ok) } err := w.Err() if err != nil { t.Errorf("expected %v, got %v", nil, err) } v := w.Value("test") if v != nil { t.Errorf("expected %v, got %v", nil, v) } w = wrapper{ router: &Router{srv: &Server{enc: DefaultResponseEncoder}}, req: nil, res: nil, w: responseWriter{}, } _, ok = w.Deadline() if ok { t.Errorf("expected %v, got %v", false, ok) } done = w.Done() if done != nil { t.Errorf("expected not nil, got %v", done) } err = w.Err() if err == nil { t.Errorf("expected not %v, got %v", nil, err) } v = w.Value("test") if v != nil { t.Errorf("expected %v, got %v", nil, v) } } ================================================ FILE: transport/http/filter.go ================================================ package http import "net/http" // FilterFunc is a function which receives a http.Handler and returns another http.Handler. type FilterFunc func(http.Handler) http.Handler // FilterChain returns a FilterFunc that specifies the chained handler for HTTP Router. func FilterChain(filters ...FilterFunc) FilterFunc { return func(next http.Handler) http.Handler { for i := len(filters) - 1; i >= 0; i-- { next = filters[i](next) } return next } } ================================================ FILE: transport/http/pprof/pprof.go ================================================ package pprof import ( "net/http" "net/http/pprof" ) // NewHandler new a pprof handler. func NewHandler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) return mux } ================================================ FILE: transport/http/redirect.go ================================================ package http type redirect struct { URL string Code int } func (r *redirect) Redirect() (string, int) { return r.URL, r.Code } func (r *redirect) Error() string { return "redirect to " + r.URL } // NewRedirect new a redirect with url, which may be a path relative to the request path. // The provided code should be in the 3xx range and is usually StatusMovedPermanently, StatusFound or StatusSeeOther. // If the Content-Type header has not been set, Redirect sets it to "text/html; charset=utf-8" and writes a small HTML body. // Setting the Content-Type header to any value, including nil, disables that behavior. func NewRedirect(url string, code int) Redirector { return &redirect{URL: url, Code: code} } ================================================ FILE: transport/http/redirect_test.go ================================================ package http import ( "net/http" "net/http/httptest" "testing" ) func TestRedirect(t *testing.T) { var ( redirectURL = "/redirect" redirectCode = 302 ) r := httptest.NewRequest(http.MethodPost, "/test", nil) w := httptest.NewRecorder() _ = DefaultResponseEncoder(w, r, NewRedirect(redirectURL, redirectCode)) if w.Code != redirectCode { t.Fatalf("want %d but got %d", redirectCode, w.Code) } if v := w.Header().Get("Location"); v != redirectURL { t.Fatalf("want %s but got %s", redirectURL, v) } } ================================================ FILE: transport/http/resolver.go ================================================ package http import ( "context" "errors" "net/url" "strings" "time" "github.com/google/uuid" "github.com/go-kratos/aegis/subset" "github.com/go-kratos/kratos/v2/internal/endpoint" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) // Target is resolver target type Target struct { Scheme string Authority string Endpoint string } func parseTarget(endpoint string, insecure bool) (*Target, error) { if !strings.Contains(endpoint, "://") { if insecure { endpoint = "http://" + endpoint } else { endpoint = "https://" + endpoint } } u, err := url.Parse(endpoint) if err != nil { return nil, err } target := &Target{Scheme: u.Scheme, Authority: u.Host} if len(u.Path) > 1 { target.Endpoint = u.Path[1:] } return target, nil } type resolver struct { rebalancer selector.Rebalancer target *Target watcher registry.Watcher selectorKey string subsetSize int insecure bool } func newResolver(ctx context.Context, discovery registry.Discovery, target *Target, rebalancer selector.Rebalancer, block, insecure bool, subsetSize int, ) (*resolver, error) { // this is new resolver watcher, err := discovery.Watch(ctx, target.Endpoint) if err != nil { return nil, err } r := &resolver{ target: target, watcher: watcher, rebalancer: rebalancer, insecure: insecure, selectorKey: uuid.New().String(), subsetSize: subsetSize, } if block { done := make(chan error, 1) go func() { for { services, err := watcher.Next() if err != nil { done <- err return } if r.update(services) { done <- nil return } } }() select { case err := <-done: if err != nil { stopErr := watcher.Stop() if stopErr != nil { log.Errorf("failed to http client watch stop: %v, error: %+v", target, stopErr) } return nil, err } case <-ctx.Done(): log.Errorf("http client watch service %v reaching context deadline!", target) stopErr := watcher.Stop() if stopErr != nil { log.Errorf("failed to http client watch stop: %v, error: %+v", target, stopErr) } return nil, ctx.Err() } } go func() { for { services, err := watcher.Next() if err != nil { if errors.Is(err, context.Canceled) { return } log.Errorf("http client watch service %v got unexpected error:=%v", target, err) time.Sleep(time.Second) continue } r.update(services) } }() return r, nil } func (r *resolver) update(services []*registry.ServiceInstance) bool { filtered := make([]*registry.ServiceInstance, 0, len(services)) for _, ins := range services { ept, err := endpoint.ParseEndpoint(ins.Endpoints, endpoint.Scheme("http", !r.insecure)) if err != nil { log.Errorf("Failed to parse (%v) discovery endpoint: %v error %v", r.target, ins.Endpoints, err) continue } if ept == "" { continue } filtered = append(filtered, ins) } if r.subsetSize != 0 { filtered = subset.Subset(r.selectorKey, filtered, r.subsetSize) } nodes := make([]selector.Node, 0, len(filtered)) for _, ins := range filtered { ept, _ := endpoint.ParseEndpoint(ins.Endpoints, endpoint.Scheme("http", !r.insecure)) nodes = append(nodes, selector.NewNode("http", ept, ins)) } if len(nodes) == 0 { log.Warnf("[http resolver]Zero endpoint found,refused to write,set: %s ins: %v", r.target.Endpoint, nodes) return false } r.rebalancer.Apply(nodes) return true } func (r *resolver) Close() error { return r.watcher.Stop() } ================================================ FILE: transport/http/resolver_test.go ================================================ package http import ( "context" "errors" "fmt" "reflect" "strconv" "sync" "testing" "time" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/selector" ) func TestParseTarget(t *testing.T) { target, err := parseTarget("localhost:8000", true) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual(&Target{Scheme: "http", Authority: "localhost:8000"}, target) { t.Errorf("expect %v, got %v", &Target{Scheme: "http", Authority: "localhost:8000"}, target) } target, err = parseTarget("discovery:///demo", true) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual(&Target{Scheme: "discovery", Authority: "", Endpoint: "demo"}, target) { t.Errorf("expect %v, got %v", &Target{Scheme: "discovery", Authority: "", Endpoint: "demo"}, target) } target, err = parseTarget("127.0.0.1:8000", true) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual(&Target{Scheme: "http", Authority: "127.0.0.1:8000"}, target) { t.Errorf("expect %v, got %v", &Target{Scheme: "http", Authority: "127.0.0.1:8000"}, target) } target, err = parseTarget("https://127.0.0.1:8000", false) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual(&Target{Scheme: "https", Authority: "127.0.0.1:8000"}, target) { t.Errorf("expect %v, got %v", &Target{Scheme: "https", Authority: "127.0.0.1:8000"}, target) } target, err = parseTarget("127.0.0.1:8000", false) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if !reflect.DeepEqual(&Target{Scheme: "https", Authority: "127.0.0.1:8000"}, target) { t.Errorf("expect %v, got %v", &Target{Scheme: "https", Authority: "127.0.0.1:8000"}, target) } } type mockRebalancer struct{} func (m *mockRebalancer) Apply(_ []selector.Node) {} type mockDiscoveries struct { isSecure bool nextErr bool stopErr bool } func (d *mockDiscoveries) GetService(_ context.Context, _ string) ([]*registry.ServiceInstance, error) { return nil, nil } const errServiceName = "needErr" func (d *mockDiscoveries) Watch(ctx context.Context, serviceName string) (registry.Watcher, error) { if serviceName == errServiceName { return nil, errors.New("mock test service name watch err") } return &mockWatch{ctx: ctx, isSecure: d.isSecure, nextErr: d.nextErr, stopErr: d.stopErr}, nil } type mockWatch struct { ctx context.Context isSecure bool count int nextErr bool stopErr bool lock sync.Mutex } func (m *mockWatch) Next() ([]*registry.ServiceInstance, error) { select { case <-m.ctx.Done(): return nil, m.ctx.Err() default: } m.lock.Lock() defer m.lock.Unlock() if m.nextErr { return nil, errors.New("mock test error") } if m.count == 1 { return nil, errors.New("mock test error") } m.count++ instance := ®istry.ServiceInstance{ ID: "1", Name: "kratos", Version: "v1", Metadata: map[string]string{}, Endpoints: []string{fmt.Sprintf("http://127.0.0.1:9001?isSecure=%s", strconv.FormatBool(m.isSecure))}, } if m.count > 3 { time.Sleep(time.Millisecond * 500) } return []*registry.ServiceInstance{instance}, nil } func (m *mockWatch) Stop() error { m.lock.Lock() defer m.lock.Unlock() if m.stopErr { return errors.New("mock test error") } // 标记 next 需要报错 m.nextErr = true return nil } func TestResolver(t *testing.T) { ta, err := parseTarget("discovery://helloworld", true) if err != nil { t.Errorf("parse err %v", err) return } cancelCtx, cancel := context.WithCancel(context.Background()) defer cancel() // 异步 无需报错 r, err := newResolver(cancelCtx, &mockDiscoveries{true, false, false}, ta, &mockRebalancer{}, false, false, 25) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if r != nil { _ = r.Close() } // 同步 一切正常运行 r, err = newResolver(cancelCtx, &mockDiscoveries{false, false, false}, ta, &mockRebalancer{}, true, true, 25) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if r != nil { _ = r.Close() } // 同步 但是 next 出错 以及 stop 出错 r, err = newResolver(cancelCtx, &mockDiscoveries{false, true, true}, ta, &mockRebalancer{}, true, true, 25) if err == nil { t.Errorf("expect err, got nil") } if r != nil { _ = r.Close() } // 同步 service name watch 失败 r, err = newResolver(cancelCtx, &mockDiscoveries{false, true, true}, &Target{ Scheme: "discovery", Endpoint: errServiceName, }, &mockRebalancer{}, true, true, 25) if err == nil { t.Errorf("expect err, got nil") } if r != nil { _ = r.Close() } cancel() // 此处应该打印出来 context.Canceled r, err = newResolver(cancelCtx, &mockDiscoveries{false, false, false}, ta, &mockRebalancer{}, false, false, 25) if err != nil { t.Errorf("expect %v, got %v", nil, err) } if r != nil { _ = r.Close() } // 同步 但是服务取消,此时需要报错 r, err = newResolver(cancelCtx, &mockDiscoveries{false, false, true}, ta, &mockRebalancer{}, true, true, 25) if err == nil { t.Errorf("expect ctx cancel err, got nil") } if r != nil { _ = r.Close() } time.Sleep(100 * time.Millisecond) } ================================================ FILE: transport/http/router.go ================================================ package http import ( "net/http" "path" ) // WalkRouteFunc is the type of the function called for each route visited by Walk. type WalkRouteFunc func(RouteInfo) error // RouteInfo is an HTTP route info. type RouteInfo struct { Path string Method string } // HandlerFunc defines a function to serve HTTP requests. type HandlerFunc func(Context) error // Router is an HTTP router. type Router struct { prefix string srv *Server filters []FilterFunc } func newRouter(prefix string, srv *Server, filters ...FilterFunc) *Router { r := &Router{ prefix: prefix, srv: srv, filters: filters, } return r } // Group returns a new router group. func (r *Router) Group(prefix string, filters ...FilterFunc) *Router { var newFilters []FilterFunc newFilters = append(newFilters, r.filters...) newFilters = append(newFilters, filters...) return newRouter(path.Join(r.prefix, prefix), r.srv, newFilters...) } // Handle registers a new route with a matcher for the URL path and method. func (r *Router) Handle(method, relativePath string, h HandlerFunc, filters ...FilterFunc) { next := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { ctx := &wrapper{router: r} ctx.Reset(res, req) if err := h(ctx); err != nil { r.srv.ene(res, req, err) } })) next = FilterChain(filters...)(next) next = FilterChain(r.filters...)(next) r.srv.router.Handle(path.Join(r.prefix, relativePath), next).Methods(method) } // GET registers a new GET route for a path with matching handler in the router. func (r *Router) GET(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodGet, path, h, m...) } // HEAD registers a new HEAD route for a path with matching handler in the router. func (r *Router) HEAD(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodHead, path, h, m...) } // POST registers a new POST route for a path with matching handler in the router. func (r *Router) POST(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodPost, path, h, m...) } // PUT registers a new PUT route for a path with matching handler in the router. func (r *Router) PUT(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodPut, path, h, m...) } // PATCH registers a new PATCH route for a path with matching handler in the router. func (r *Router) PATCH(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodPatch, path, h, m...) } // DELETE registers a new DELETE route for a path with matching handler in the router. func (r *Router) DELETE(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodDelete, path, h, m...) } // CONNECT registers a new CONNECT route for a path with matching handler in the router. func (r *Router) CONNECT(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodConnect, path, h, m...) } // OPTIONS registers a new OPTIONS route for a path with matching handler in the router. func (r *Router) OPTIONS(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodOptions, path, h, m...) } // TRACE registers a new TRACE route for a path with matching handler in the router. func (r *Router) TRACE(path string, h HandlerFunc, m ...FilterFunc) { r.Handle(http.MethodTrace, path, h, m...) } ================================================ FILE: transport/http/router_test.go ================================================ package http import ( "context" "encoding/json" "errors" "fmt" "log" "net/http" "reflect" "runtime" "strings" "sync" "testing" "time" "github.com/go-kratos/kratos/v2/internal/host" ) const appJSONStr = "application/json" type User struct { Name string `json:"name"` } func corsFilter(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { log.Println("cors:", r.Method, r.RequestURI) w.Header().Set("Access-Control-Allow-Methods", r.Method) return } next.ServeHTTP(w, r) }) } func authFilter(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here log.Println("auth:", r.Method, r.RequestURI) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) }) } func loggingFilter(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Do stuff here log.Println("logging:", r.Method, r.RequestURI) // Call the next handler, which can be another middleware in the chain, or the final handler. next.ServeHTTP(w, r) }) } func TestRoute(t *testing.T) { ctx := context.Background() srv := NewServer( Filter(corsFilter, loggingFilter), ) route := srv.Route("/v1") route.GET("/users/{name}", func(ctx Context) error { u := new(User) u.Name = ctx.Vars().Get("name") return ctx.Result(200, u) }, authFilter) route.POST("/users", func(ctx Context) error { u := new(User) if err := ctx.Bind(u); err != nil { return err } return ctx.Result(201, u) }) route.PUT("/users", func(ctx Context) error { u := new(User) if err := ctx.Bind(u); err != nil { return err } h := ctx.Middleware(func(context.Context, any) (any, error) { return u, nil }) return ctx.Returns(h(ctx, u)) }) if e, err := srv.Endpoint(); err != nil || e == nil { t.Fatal(e, err) } go func() { if err := srv.Start(ctx); err != nil { panic(err) } }() time.Sleep(time.Second) testRoute(t, srv) _ = srv.Stop(ctx) } func testRoute(t *testing.T, srv *Server) { port, ok := host.Port(srv.lis) if !ok { t.Fatalf("extract port error: %v", srv.lis) } base := fmt.Sprintf("http://127.0.0.1:%d/v1", port) // GET resp, err := http.Get(base + "/users/foo") if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("code: %d", resp.StatusCode) } if v := resp.Header.Get("Content-Type"); v != appJSONStr { t.Fatalf("contentType: %s", v) } u := new(User) if err = json.NewDecoder(resp.Body).Decode(u); err != nil { t.Fatal(err) } if u.Name != "foo" { t.Fatalf("got %s want foo", u.Name) } // POST resp, err = http.Post(base+"/users", appJSONStr, strings.NewReader(`{"name":"bar"}`)) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 201 { t.Fatalf("code: %d", resp.StatusCode) } if v := resp.Header.Get("Content-Type"); v != appJSONStr { t.Fatalf("contentType: %s", v) } u = new(User) if err = json.NewDecoder(resp.Body).Decode(u); err != nil { t.Fatal(err) } if u.Name != "bar" { t.Fatalf("got %s want bar", u.Name) } // PUT req, _ := http.NewRequest(http.MethodPut, base+"/users", strings.NewReader(`{"name":"bar"}`)) req.Header.Set("Content-Type", appJSONStr) resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("code: %d", resp.StatusCode) } if v := resp.Header.Get("Content-Type"); v != appJSONStr { t.Fatalf("contentType: %s", v) } u = new(User) if err = json.NewDecoder(resp.Body).Decode(u); err != nil { t.Fatal(err) } if u.Name != "bar" { t.Fatalf("got %s want bar", u.Name) } // OPTIONS req, _ = http.NewRequest(http.MethodOptions, base+"/users", nil) resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("code: %d", resp.StatusCode) } if resp.Header.Get("Access-Control-Allow-Methods") != http.MethodOptions { t.Fatal("cors failed") } } func TestRouter_Group(t *testing.T) { r := &Router{} rr := r.Group("a", func(http.Handler) http.Handler { return nil }) if !reflect.DeepEqual("a", rr.prefix) { t.Errorf("expected %q, got %q", "a", rr.prefix) } } func TestHandle(_ *testing.T) { r := newRouter("/", NewServer()) h := func(Context) error { return nil } r.GET("/get", h) r.HEAD("/head", h) r.PATCH("/patch", h) r.DELETE("/delete", h) r.CONNECT("/connect", h) r.OPTIONS("/options", h) r.TRACE("/trace", h) } func TestRouter_ContextDataRace(t *testing.T) { runtime.GOMAXPROCS(runtime.NumCPU()) ctx := context.Background() srvPort := 38888 srvAddr := fmt.Sprintf(":%d", srvPort) srv := NewServer(Timeout(time.Millisecond*50), Address(srvAddr)) router := srv.Route("/") router.GET("/ping", func(ctx Context) error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://www.baidu.com", nil) resp, err := http.DefaultClient.Do(req) if err != nil { return ctx.String(200, err.Error()) } _ = resp.Body.Close() return ctx.String(200, "pong") }) // start server go func() { if err := srv.Start(ctx); err != nil { if errors.Is(err, http.ErrServerClosed) { return } panic(err) } }() time.Sleep(time.Second) // start client workers := 10 wg := sync.WaitGroup{} wg.Add(workers) for i := 0; i < workers; i++ { go func() { defer wg.Done() for j := 0; j < 50; j++ { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d/ping", srvPort), nil) res, err := http.DefaultClient.Do(req) if err != nil { break } _ = res.Body.Close() } }() } wg.Wait() _ = srv.Stop(ctx) t.Log("test end") } ================================================ FILE: transport/http/server.go ================================================ package http import ( "context" "crypto/tls" "errors" "net" "net/http" "net/url" "time" "github.com/gorilla/mux" "github.com/go-kratos/kratos/v2/internal/endpoint" "github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/internal/matcher" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/transport" ) var ( _ transport.Server = (*Server)(nil) _ transport.Endpointer = (*Server)(nil) _ http.Handler = (*Server)(nil) ) // ServerOption is an HTTP server option. type ServerOption func(*Server) // Network with server network. func Network(network string) ServerOption { return func(s *Server) { s.network = network } } // Address with server address. func Address(addr string) ServerOption { return func(s *Server) { s.address = addr } } // Endpoint with server address. func Endpoint(endpoint *url.URL) ServerOption { return func(s *Server) { s.endpoint = endpoint } } // Timeout with server timeout. func Timeout(timeout time.Duration) ServerOption { return func(s *Server) { s.timeout = timeout } } // Logger with server logger. // Deprecated: use global logger instead. func Logger(log.Logger) ServerOption { return func(*Server) {} } // Middleware with service middleware option. func Middleware(m ...middleware.Middleware) ServerOption { return func(o *Server) { o.middleware.Use(m...) } } // Filter with HTTP middleware option. func Filter(filters ...FilterFunc) ServerOption { return func(o *Server) { o.filters = filters } } // RequestVarsDecoder with request decoder. func RequestVarsDecoder(dec DecodeRequestFunc) ServerOption { return func(o *Server) { o.decVars = dec } } // RequestQueryDecoder with request decoder. func RequestQueryDecoder(dec DecodeRequestFunc) ServerOption { return func(o *Server) { o.decQuery = dec } } // RequestDecoder with request decoder. func RequestDecoder(dec DecodeRequestFunc) ServerOption { return func(o *Server) { o.decBody = dec } } // ResponseEncoder with response encoder. func ResponseEncoder(en EncodeResponseFunc) ServerOption { return func(o *Server) { o.enc = en } } // ErrorEncoder with error encoder. func ErrorEncoder(en EncodeErrorFunc) ServerOption { return func(o *Server) { o.ene = en } } // TLSConfig with TLS config. func TLSConfig(c *tls.Config) ServerOption { return func(o *Server) { o.tlsConf = c } } // StrictSlash is with mux's StrictSlash // If true, when the path pattern is "/path/", accessing "/path" will // redirect to the former and vice versa. func StrictSlash(strictSlash bool) ServerOption { return func(o *Server) { o.strictSlash = strictSlash } } // Listener with server lis func Listener(lis net.Listener) ServerOption { return func(s *Server) { s.lis = lis } } // PathPrefix with mux's PathPrefix, router will be replaced by a subrouter that start with prefix. func PathPrefix(prefix string) ServerOption { return func(s *Server) { s.router = s.router.PathPrefix(prefix).Subrouter() } } func NotFoundHandler(handler http.Handler) ServerOption { return func(s *Server) { s.router.NotFoundHandler = handler } } func MethodNotAllowedHandler(handler http.Handler) ServerOption { return func(s *Server) { s.router.MethodNotAllowedHandler = handler } } // Server is an HTTP server wrapper. type Server struct { *http.Server lis net.Listener tlsConf *tls.Config endpoint *url.URL err error network string address string timeout time.Duration filters []FilterFunc middleware matcher.Matcher decVars DecodeRequestFunc decQuery DecodeRequestFunc decBody DecodeRequestFunc enc EncodeResponseFunc ene EncodeErrorFunc strictSlash bool router *mux.Router } // NewServer creates an HTTP server by options. func NewServer(opts ...ServerOption) *Server { srv := &Server{ network: "tcp", address: ":0", timeout: 1 * time.Second, middleware: matcher.New(), decVars: DefaultRequestVars, decQuery: DefaultRequestQuery, decBody: DefaultRequestDecoder, enc: DefaultResponseEncoder, ene: DefaultErrorEncoder, strictSlash: true, router: mux.NewRouter(), } srv.router.NotFoundHandler = http.DefaultServeMux srv.router.MethodNotAllowedHandler = http.DefaultServeMux for _, o := range opts { o(srv) } srv.router.StrictSlash(srv.strictSlash) srv.router.Use(srv.filter()) srv.Server = &http.Server{ Handler: FilterChain(srv.filters...)(srv.router), TLSConfig: srv.tlsConf, } return srv } // Use uses a service middleware with selector. // selector: // - '/*' // - '/helloworld.v1.Greeter/*' // - '/helloworld.v1.Greeter/SayHello' func (s *Server) Use(selector string, m ...middleware.Middleware) { s.middleware.Add(selector, m...) } // WalkRoute walks the router and all its sub-routers, calling walkFn for each route in the tree. func (s *Server) WalkRoute(fn WalkRouteFunc) error { return s.router.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error { methods, err := route.GetMethods() if err != nil { return nil // ignore no methods } path, err := route.GetPathTemplate() if err != nil { return err } for _, method := range methods { if err := fn(RouteInfo{Method: method, Path: path}); err != nil { return err } } return nil }) } // WalkHandle walks the router and all its sub-routers, calling walkFn for each route in the tree. func (s *Server) WalkHandle(handle func(method, path string, handler http.HandlerFunc)) error { return s.WalkRoute(func(r RouteInfo) error { handle(r.Method, r.Path, s.ServeHTTP) return nil }) } // Route registers an HTTP router. func (s *Server) Route(prefix string, filters ...FilterFunc) *Router { return newRouter(prefix, s, filters...) } // Handle registers a new route with a matcher for the URL path. func (s *Server) Handle(path string, h http.Handler) { s.router.Handle(path, h) } // HandlePrefix registers a new route with a matcher for the URL path prefix. func (s *Server) HandlePrefix(prefix string, h http.Handler) { s.router.PathPrefix(prefix).Handler(h) } // HandleFunc registers a new route with a matcher for the URL path. func (s *Server) HandleFunc(path string, h http.HandlerFunc) { s.router.HandleFunc(path, h) } // HandleHeader registers a new route with a matcher for the header. func (s *Server) HandleHeader(key, val string, h http.HandlerFunc) { s.router.Headers(key, val).Handler(h) } // ServeHTTP should write reply headers and data to the ResponseWriter and then return. func (s *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { s.Handler.ServeHTTP(res, req) } func (s *Server) filter() mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { var ( ctx context.Context cancel context.CancelFunc ) if s.timeout > 0 { ctx, cancel = context.WithTimeout(req.Context(), s.timeout) } else { ctx, cancel = context.WithCancel(req.Context()) } defer cancel() pathTemplate := req.URL.Path if route := mux.CurrentRoute(req); route != nil { // /path/123 -> /path/{id} pathTemplate, _ = route.GetPathTemplate() } tr := &Transport{ operation: pathTemplate, pathTemplate: pathTemplate, reqHeader: headerCarrier(req.Header), replyHeader: headerCarrier(w.Header()), request: req, response: w, } if s.endpoint != nil { tr.endpoint = s.endpoint.String() } tr.request = req.WithContext(transport.NewServerContext(ctx, tr)) next.ServeHTTP(w, tr.request) }) } } // Endpoint return a real address to registry endpoint. // examples: // // https://127.0.0.1:8000 // Legacy: http://127.0.0.1:8000?isSecure=false func (s *Server) Endpoint() (*url.URL, error) { if err := s.listenAndEndpoint(); err != nil { return nil, err } return s.endpoint, nil } // Start start the HTTP server. func (s *Server) Start(ctx context.Context) error { if err := s.listenAndEndpoint(); err != nil { return err } s.BaseContext = func(net.Listener) context.Context { return ctx } log.Infof("[HTTP] server listening on: %s", s.lis.Addr().String()) var err error if s.tlsConf != nil { err = s.ServeTLS(s.lis, "", "") } else { err = s.Serve(s.lis) } if !errors.Is(err, http.ErrServerClosed) { return err } return nil } // Stop stop the HTTP server. func (s *Server) Stop(ctx context.Context) error { log.Info("[HTTP] server stopping") err := s.Shutdown(ctx) if err != nil { if ctx.Err() != nil { log.Warn("[HTTP] server couldn't stop gracefully in time, doing force stop") err = s.Close() } } return err } func (s *Server) listenAndEndpoint() error { if s.lis == nil { lis, err := net.Listen(s.network, s.address) if err != nil { s.err = err return err } s.lis = lis } if s.endpoint == nil { addr, err := host.Extract(s.address, s.lis) if err != nil { s.err = err return err } s.endpoint = endpoint.NewEndpoint(endpoint.Scheme("http", s.tlsConf != nil), addr) } return s.err } ================================================ FILE: transport/http/server_test.go ================================================ package http import ( "bytes" "context" "crypto/tls" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "reflect" "strings" "sync" "testing" "time" kratoserrors "github.com/go-kratos/kratos/v2/errors" "github.com/go-kratos/kratos/v2/internal/host" "github.com/go-kratos/kratos/v2/log" ) var h = func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(testData{Path: r.RequestURI}) } type testKey struct{} type testData struct { Path string `json:"path"` } // handleFuncWrapper is a wrapper for http.HandlerFunc to implement http.Handler type handleFuncWrapper struct { fn http.HandlerFunc } func (x *handleFuncWrapper) ServeHTTP(writer http.ResponseWriter, request *http.Request) { x.fn.ServeHTTP(writer, request) } func newHandleFuncWrapper(fn http.HandlerFunc) http.Handler { return &handleFuncWrapper{fn: fn} } func TestServeHTTP(t *testing.T) { ln, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) } mux := NewServer(Listener(ln)) mux.HandleFunc("/index", h) mux.Route("/errors").GET("/cause", func(Context) error { return kratoserrors.BadRequest("xxx", "zzz"). WithMetadata(map[string]string{"foo": "bar"}). WithCause(errors.New("error cause")) }) if err = mux.WalkRoute(func(r RouteInfo) error { t.Logf("WalkRoute: %+v", r) return nil }); err != nil { t.Fatal(err) } if e, err := mux.Endpoint(); err != nil || e == nil || strings.HasSuffix(e.Host, ":0") { t.Fatal(e, err) } srv := http.Server{Handler: mux} go func() { if err := srv.Serve(ln); err != nil { if kratoserrors.Is(err, http.ErrServerClosed) { return } panic(err) } }() time.Sleep(time.Second) if err := srv.Shutdown(context.Background()); err != nil { t.Log(err) } } func TestServer(t *testing.T) { ctx := context.Background() srv := NewServer() srv.Handle("/index", newHandleFuncWrapper(h)) srv.HandleFunc("/index/{id:[0-9]+}", h) srv.HandlePrefix("/test/prefix", newHandleFuncWrapper(h)) srv.HandleHeader("content-type", "application/grpc-web+json", func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(testData{Path: r.RequestURI}) }) srv.Route("/errors").GET("/cause", func(Context) error { return kratoserrors.BadRequest("xxx", "zzz"). WithMetadata(map[string]string{"foo": "bar"}). WithCause(errors.New("error cause")) }) if e, err := srv.Endpoint(); err != nil || e == nil || strings.HasSuffix(e.Host, ":0") { t.Fatal(e, err) } go func() { if err := srv.Start(ctx); err != nil { panic(err) } }() time.Sleep(time.Second) testHeader(t, srv) testClient(t, srv) testAccept(t, srv) time.Sleep(time.Second) if srv.Stop(ctx) != nil { t.Errorf("expected nil got %v", srv.Stop(ctx)) } } func testAccept(t *testing.T, srv *Server) { tests := []struct { method string path string contentType string }{ {http.MethodGet, "/errors/cause", "application/json"}, {http.MethodGet, "/errors/cause", "application/proto"}, } e, err := srv.Endpoint() if err != nil { t.Errorf("expected nil got %v", err) } client, err := NewClient(context.Background(), WithEndpoint(e.Host)) if err != nil { t.Errorf("expected nil got %v", err) } for _, test := range tests { req, err := http.NewRequest(test.method, e.String()+test.path, nil) if err != nil { t.Errorf("expected nil got %v", err) } req.Header.Set("Content-Type", test.contentType) resp, err := client.Do(req) if kratoserrors.Code(err) != 400 { t.Errorf("expected 400 got %v", err) } if err == nil { resp.Body.Close() } } } func testHeader(t *testing.T, srv *Server) { e, err := srv.Endpoint() if err != nil { t.Errorf("expected nil got %v", err) } client, err := NewClient(context.Background(), WithEndpoint(e.Host)) if err != nil { t.Errorf("expected nil got %v", err) } req, err := http.NewRequest(http.MethodGet, e.String()+"/index", nil) if err != nil { t.Errorf("expected nil got %v", err) } req.Header.Set("content-type", "application/grpc-web+json") resp, err := client.Do(req) if err != nil { t.Errorf("expected nil got %v", err) } resp.Body.Close() } func testClient(t *testing.T, srv *Server) { tests := []struct { method string path string code int }{ {http.MethodGet, "/index", http.StatusOK}, {http.MethodPut, "/index", http.StatusOK}, {http.MethodPost, "/index", http.StatusOK}, {http.MethodPatch, "/index", http.StatusOK}, {http.MethodDelete, "/index", http.StatusOK}, {http.MethodGet, "/index/1", http.StatusOK}, {http.MethodPut, "/index/1", http.StatusOK}, {http.MethodPost, "/index/1", http.StatusOK}, {http.MethodPatch, "/index/1", http.StatusOK}, {http.MethodDelete, "/index/1", http.StatusOK}, {http.MethodGet, "/index/notfound", http.StatusNotFound}, {http.MethodGet, "/errors/cause", http.StatusBadRequest}, {http.MethodGet, "/test/prefix/123111", http.StatusOK}, } e, err := srv.Endpoint() if err != nil { t.Fatal(err) } client, err := NewClient(context.Background(), WithEndpoint(e.Host)) if err != nil { t.Fatal(err) } defer client.Close() for _, test := range tests { var res testData reqURL := e.String() + test.path req, err := http.NewRequest(test.method, reqURL, nil) if err != nil { t.Fatal(err) } resp, err := client.Do(req) if kratoserrors.Code(err) != test.code { t.Fatalf("want %v, but got %v", test, err) } if err != nil { continue } if resp.StatusCode != 200 { _ = resp.Body.Close() t.Fatalf("http status got %d", resp.StatusCode) } content, err := io.ReadAll(resp.Body) _ = resp.Body.Close() if err != nil { t.Fatalf("read resp error %v", err) } err = json.Unmarshal(content, &res) if err != nil { t.Fatalf("unmarshal resp error %v", err) } if res.Path != test.path { t.Errorf("expected %s got %s", test.path, res.Path) } } for _, test := range tests { var res testData err := client.Invoke(context.Background(), test.method, test.path, nil, &res) if kratoserrors.Code(err) != test.code { t.Fatalf("want %v, but got %v", test, err) } if err != nil { continue } if res.Path != test.path { t.Errorf("expected %s got %s", test.path, res.Path) } } } func BenchmarkServer(b *testing.B) { fn := func(w http.ResponseWriter, r *http.Request) { data := &testData{Path: r.RequestURI} _ = json.NewEncoder(w).Encode(data) if r.Context().Value(testKey{}) != "test" { w.WriteHeader(500) } } ctx := context.Background() ctx = context.WithValue(ctx, testKey{}, "test") srv := NewServer() srv.HandleFunc("/index", fn) go func() { if err := srv.Start(ctx); err != nil { panic(err) } }() time.Sleep(time.Second) port, ok := host.Port(srv.lis) if !ok { b.Errorf("expected port got %v", srv.lis) } client, err := NewClient(context.Background(), WithEndpoint(fmt.Sprintf("127.0.0.1:%d", port))) if err != nil { b.Errorf("expected nil got %v", err) } b.ResetTimer() for i := 0; i < b.N; i++ { var res testData err := client.Invoke(context.Background(), http.MethodPost, "/index", nil, &res) if err != nil { b.Errorf("expected nil got %v", err) } } _ = srv.Stop(ctx) } func TestNetwork(t *testing.T) { o := &Server{} v := "abc" Network(v)(o) if !reflect.DeepEqual(v, o.network) { t.Errorf("expected %v got %v", v, o.network) } } func TestAddress(t *testing.T) { o := &Server{} v := "abc" Address(v)(o) if !reflect.DeepEqual(v, o.address) { t.Errorf("expected %v got %v", v, o.address) } } func TestTimeout(t *testing.T) { o := &Server{} v := time.Duration(123) Timeout(v)(o) if !reflect.DeepEqual(v, o.timeout) { t.Errorf("expected %v got %v", v, o.timeout) } } func TestRequestDecoder(t *testing.T) { o := &Server{} v := func(*http.Request, any) error { return nil } RequestDecoder(v)(o) if o.decBody == nil { t.Errorf("expected nil got %v", o.decBody) } } func TestResponseEncoder(t *testing.T) { o := &Server{} v := func(http.ResponseWriter, *http.Request, any) error { return nil } ResponseEncoder(v)(o) if o.enc == nil { t.Errorf("expected nil got %v", o.enc) } } func TestErrorEncoder(t *testing.T) { o := &Server{} v := func(http.ResponseWriter, *http.Request, error) {} ErrorEncoder(v)(o) if o.ene == nil { t.Errorf("expected nil got %v", o.ene) } } func TestTLSConfig(t *testing.T) { o := &Server{} v := &tls.Config{} TLSConfig(v)(o) if !reflect.DeepEqual(v, o.tlsConf) { t.Errorf("expected %v got %v", v, o.tlsConf) } } func TestStrictSlash(t *testing.T) { o := &Server{} v := true StrictSlash(v)(o) if !reflect.DeepEqual(v, o.strictSlash) { t.Errorf("expected %v got %v", v, o.tlsConf) } } func TestListener(t *testing.T) { lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) } s := &Server{} Listener(lis)(s) if !reflect.DeepEqual(s.lis, lis) { t.Errorf("expected %v got %v", lis, s.lis) } if e, err := s.Endpoint(); err != nil || e == nil { t.Errorf("expected not empty") } } func TestNotFoundHandler(t *testing.T) { mux := http.NewServeMux() srv := NewServer(NotFoundHandler(mux)) if !reflect.DeepEqual(srv.router.NotFoundHandler, mux) { t.Errorf("expected %v got %v", mux, srv.router.NotFoundHandler) } } func TestMethodNotAllowedHandler(t *testing.T) { mux := http.NewServeMux() srv := NewServer(MethodNotAllowedHandler(mux)) if !reflect.DeepEqual(srv.router.MethodNotAllowedHandler, mux) { t.Errorf("expected %v got %v", mux, srv.router.MethodNotAllowedHandler) } } func TestStop(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() tests := []struct { name string sleep time.Duration ctx context.Context cancel context.CancelFunc wantForceStop bool }{ { name: "normal", sleep: 0, ctx: context.Background(), cancel: func() {}, wantForceStop: false, }, { name: "timeout", sleep: 2 * time.Second, ctx: timeoutCtx, cancel: cancel, wantForceStop: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { old := log.GetLogger() defer log.SetLogger(old) // Create a logger to capture logs var logs safeBytesBuffer log.SetLogger(log.NewStdLogger(&logs)) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t := time.NewTimer(tt.sleep) defer t.Stop() select { case <-t.C: case <-r.Context().Done(): } w.WriteHeader(http.StatusOK) })) defer testServer.Close() go func() { resp, err := http.Get(testServer.URL) if err != nil { return } _ = resp.Body.Close() }() time.Sleep(100 * time.Millisecond) s := &Server{ Server: testServer.Config, } tt.cancel() err := s.Stop(tt.ctx) if err != nil { t.Errorf("Expected no error, got %v", err) return } // Check if the stop was forced or graceful if tt.wantForceStop { if !strings.Contains(logs.String(), "force stop") { t.Errorf("Expected force stop\n%s", logs.String()) } } else { if strings.Contains(logs.String(), "force stop") { t.Errorf("Expected graceful stop\n%s", logs.String()) } } }) } } type safeBytesBuffer struct { mu sync.Mutex buf bytes.Buffer } func (b *safeBytesBuffer) Write(p []byte) (n int, err error) { b.mu.Lock() defer b.mu.Unlock() return b.buf.Write(p) } func (b *safeBytesBuffer) String() string { b.mu.Lock() defer b.mu.Unlock() return b.buf.String() } ================================================ FILE: transport/http/status/status.go ================================================ package status import ( "net/http" "google.golang.org/grpc/codes" ) const ( // ClientClosed is non-standard http status code, // which defined by nginx. // https://httpstatus.in/499/ ClientClosed = 499 ) // Converter is a status converter. type Converter interface { // ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. ToGRPCCode(code int) codes.Code // FromGRPCCode converts a gRPC error code into the corresponding HTTP response status. FromGRPCCode(code codes.Code) int } type statusConverter struct{} // DefaultConverter default converter. var DefaultConverter Converter = statusConverter{} // ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. // See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto func (c statusConverter) ToGRPCCode(code int) codes.Code { switch code { case http.StatusOK: return codes.OK case http.StatusBadRequest: return codes.InvalidArgument case http.StatusUnauthorized: return codes.Unauthenticated case http.StatusForbidden: return codes.PermissionDenied case http.StatusNotFound: return codes.NotFound case http.StatusConflict: return codes.Aborted case http.StatusTooManyRequests: return codes.ResourceExhausted case http.StatusInternalServerError: return codes.Internal case http.StatusNotImplemented: return codes.Unimplemented case http.StatusServiceUnavailable: return codes.Unavailable case http.StatusGatewayTimeout: return codes.DeadlineExceeded case ClientClosed: return codes.Canceled case http.StatusPreconditionFailed: return codes.FailedPrecondition } return codes.Unknown } // FromGRPCCode converts a gRPC error code into the corresponding HTTP response status. // See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto func (c statusConverter) FromGRPCCode(code codes.Code) int { switch code { case codes.OK: return http.StatusOK case codes.Canceled: return ClientClosed case codes.Unknown: return http.StatusInternalServerError case codes.InvalidArgument: return http.StatusBadRequest case codes.DeadlineExceeded: return http.StatusGatewayTimeout case codes.NotFound: return http.StatusNotFound case codes.AlreadyExists: return http.StatusConflict case codes.PermissionDenied: return http.StatusForbidden case codes.Unauthenticated: return http.StatusUnauthorized case codes.ResourceExhausted: return http.StatusTooManyRequests case codes.Aborted: return http.StatusConflict case codes.OutOfRange: return http.StatusBadRequest case codes.Unimplemented: return http.StatusNotImplemented case codes.Internal: return http.StatusInternalServerError case codes.Unavailable: return http.StatusServiceUnavailable case codes.DataLoss: return http.StatusInternalServerError case codes.FailedPrecondition: return http.StatusPreconditionFailed } return http.StatusInternalServerError } // ToGRPCCode converts an HTTP error code into the corresponding gRPC response status. func ToGRPCCode(code int) codes.Code { return DefaultConverter.ToGRPCCode(code) } // FromGRPCCode converts a gRPC error code into the corresponding HTTP response status. func FromGRPCCode(code codes.Code) int { return DefaultConverter.FromGRPCCode(code) } ================================================ FILE: transport/http/status/status_test.go ================================================ package status import ( "net/http" "testing" "google.golang.org/grpc/codes" ) func TestToGRPCCode(t *testing.T) { tests := []struct { name string code int want codes.Code }{ {"http.StatusOK", http.StatusOK, codes.OK}, {"http.StatusBadRequest", http.StatusBadRequest, codes.InvalidArgument}, {"http.StatusUnauthorized", http.StatusUnauthorized, codes.Unauthenticated}, {"http.StatusForbidden", http.StatusForbidden, codes.PermissionDenied}, {"http.StatusNotFound", http.StatusNotFound, codes.NotFound}, {"http.StatusConflict", http.StatusConflict, codes.Aborted}, {"http.StatusTooManyRequests", http.StatusTooManyRequests, codes.ResourceExhausted}, {"http.StatusInternalServerError", http.StatusInternalServerError, codes.Internal}, {"http.StatusNotImplemented", http.StatusNotImplemented, codes.Unimplemented}, {"http.StatusServiceUnavailable", http.StatusServiceUnavailable, codes.Unavailable}, {"http.StatusGatewayTimeout", http.StatusGatewayTimeout, codes.DeadlineExceeded}, {"StatusClientClosed", ClientClosed, codes.Canceled}, {"http.StatusPreconditionFailed", http.StatusPreconditionFailed, codes.FailedPrecondition}, {"else", 100000, codes.Unknown}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ToGRPCCode(tt.code); got != tt.want { t.Errorf("GRPCCodeFromStatus() = %v, want %v", got, tt.want) } }) } } func TestFromGRPCCode(t *testing.T) { tests := []struct { name string code codes.Code want int }{ {"codes.OK", codes.OK, http.StatusOK}, {"codes.Canceled", codes.Canceled, ClientClosed}, {"codes.Unknown", codes.Unknown, http.StatusInternalServerError}, {"codes.InvalidArgument", codes.InvalidArgument, http.StatusBadRequest}, {"codes.DeadlineExceeded", codes.DeadlineExceeded, http.StatusGatewayTimeout}, {"codes.NotFound", codes.NotFound, http.StatusNotFound}, {"codes.AlreadyExists", codes.AlreadyExists, http.StatusConflict}, {"codes.PermissionDenied", codes.PermissionDenied, http.StatusForbidden}, {"codes.Unauthenticated", codes.Unauthenticated, http.StatusUnauthorized}, {"codes.ResourceExhausted", codes.ResourceExhausted, http.StatusTooManyRequests}, {"codes.FailedPrecondition", codes.FailedPrecondition, http.StatusPreconditionFailed}, {"codes.Aborted", codes.Aborted, http.StatusConflict}, {"codes.OutOfRange", codes.OutOfRange, http.StatusBadRequest}, {"codes.Unimplemented", codes.Unimplemented, http.StatusNotImplemented}, {"codes.Internal", codes.Internal, http.StatusInternalServerError}, {"codes.Unavailable", codes.Unavailable, http.StatusServiceUnavailable}, {"codes.DataLoss", codes.DataLoss, http.StatusInternalServerError}, {"else", codes.Code(10000), http.StatusInternalServerError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := FromGRPCCode(tt.code); got != tt.want { t.Errorf("StatusFromGRPCCode() = %v, want %v", got, tt.want) } }) } } ================================================ FILE: transport/http/transport.go ================================================ package http import ( "context" "net/http" "github.com/go-kratos/kratos/v2/transport" ) var _ Transporter = (*Transport)(nil) var _ ResponseTransporter = (*Transport)(nil) // Transporter is http Transporter type Transporter interface { transport.Transporter Request() *http.Request PathTemplate() string } // ResponseTransporter extends Transporter with HTTP response access // This interface provides access to the http.ResponseWriter for use cases // like file downloads, streaming responses, or direct response manipulation. type ResponseTransporter interface { Transporter Response() http.ResponseWriter } // Transport is an HTTP transport. type Transport struct { endpoint string operation string reqHeader headerCarrier replyHeader headerCarrier request *http.Request response http.ResponseWriter pathTemplate string } // Kind returns the transport kind. func (tr *Transport) Kind() transport.Kind { return transport.KindHTTP } // Endpoint returns the transport endpoint. func (tr *Transport) Endpoint() string { return tr.endpoint } // Operation returns the transport operation. func (tr *Transport) Operation() string { return tr.operation } // Request returns the HTTP request. func (tr *Transport) Request() *http.Request { return tr.request } // RequestHeader returns the request header. func (tr *Transport) RequestHeader() transport.Header { return tr.reqHeader } // Response returns the HTTP response. func (tr *Transport) Response() http.ResponseWriter { return tr.response } // ReplyHeader returns the reply header. func (tr *Transport) ReplyHeader() transport.Header { return tr.replyHeader } // PathTemplate returns the http path template. func (tr *Transport) PathTemplate() string { return tr.pathTemplate } // SetOperation sets the transport operation. func SetOperation(ctx context.Context, op string) { if tr, ok := transport.FromServerContext(ctx); ok { if tr, ok := tr.(*Transport); ok { tr.operation = op } } } // SetCookie adds a Set-Cookie header to the provided [ResponseWriter]'s headers. // The provided cookie must have a valid Name. Invalid cookies may be // silently dropped. func SetCookie(ctx context.Context, cookie *http.Cookie) { if tr, ok := transport.FromServerContext(ctx); ok { if tr, ok := tr.(*Transport); ok { http.SetCookie(tr.response, cookie) } } } // RequestFromServerContext returns request from context. func RequestFromServerContext(ctx context.Context) (*http.Request, bool) { if tr, ok := transport.FromServerContext(ctx); ok { if htr, ok := tr.(Transporter); ok { return htr.Request(), true } } return nil, false } type headerCarrier http.Header // Get returns the value associated with the passed key. func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key) } // Set stores the key-value pair. func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) } // Add append value to key-values pair. func (hc headerCarrier) Add(key string, value string) { http.Header(hc).Add(key, value) } // Keys lists the keys stored in this carrier. func (hc headerCarrier) Keys() []string { keys := make([]string, 0, len(hc)) for k := range http.Header(hc) { keys = append(keys, k) } return keys } // Values returns a slice of values associated with the passed key. func (hc headerCarrier) Values(key string) []string { return http.Header(hc).Values(key) } // ResponseWriterFromServerContext returns the http.ResponseWriter from context if available. // This function provides backward compatibility and safe access to the ResponseWriter. // Returns nil if the transport doesn't implement ResponseTransporter. func ResponseWriterFromServerContext(ctx context.Context) (http.ResponseWriter, bool) { if tr, ok := transport.FromServerContext(ctx); ok { if httpTr, ok := tr.(ResponseTransporter); ok { return httpTr.Response(), true } } return nil, false } ================================================ FILE: transport/http/transport_test.go ================================================ package http import ( "context" "net/http" "reflect" "sort" "testing" "github.com/go-kratos/kratos/v2/transport" ) func TestTransport_Kind(t *testing.T) { o := &Transport{} if !reflect.DeepEqual(transport.KindHTTP, o.Kind()) { t.Errorf("expect %v, got %v", transport.KindHTTP, o.Kind()) } } func TestTransport_Endpoint(t *testing.T) { v := "hello" o := &Transport{endpoint: v} if !reflect.DeepEqual(v, o.Endpoint()) { t.Errorf("expect %v, got %v", v, o.Endpoint()) } } func TestTransport_Operation(t *testing.T) { v := "hello" o := &Transport{operation: v} if !reflect.DeepEqual(v, o.Operation()) { t.Errorf("expect %v, got %v", v, o.Operation()) } } func TestTransport_Request(t *testing.T) { v := &http.Request{} o := &Transport{request: v} if !reflect.DeepEqual(v, o.Request()) { t.Errorf("expect %v, got %v", v, o.Request()) } } func TestTransport_RequestHeader(t *testing.T) { v := headerCarrier{} v.Set("a", "1") o := &Transport{reqHeader: v} if !reflect.DeepEqual("1", o.RequestHeader().Get("a")) { t.Errorf("expect %v, got %v", "1", o.RequestHeader().Get("a")) } } func TestTransport_Response(t *testing.T) { v := http.ResponseWriter(nil) o := &Transport{response: v} if !reflect.DeepEqual(v, o.Response()) { t.Errorf("expect %v, got %v", v, o.Response()) } } func TestTransport_ReplyHeader(t *testing.T) { v := headerCarrier{} v.Set("a", "1") o := &Transport{replyHeader: v} if !reflect.DeepEqual("1", o.ReplyHeader().Get("a")) { t.Errorf("expect %v, got %v", "1", o.ReplyHeader().Get("a")) } } func TestTransport_PathTemplate(t *testing.T) { v := "template" o := &Transport{pathTemplate: v} if !reflect.DeepEqual(v, o.PathTemplate()) { t.Errorf("expect %v, got %v", v, o.PathTemplate()) } } func TestHeaderCarrier_Keys(t *testing.T) { v := headerCarrier{} v.Set("abb", "1") v.Set("bcc", "2") want := []string{"Abb", "Bcc"} keys := v.Keys() sort.Slice(want, func(i, j int) bool { return want[i] < want[j] }) sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) if !reflect.DeepEqual(want, keys) { t.Errorf("expect %v, got %v", want, keys) } } func TestSetOperation(t *testing.T) { tr := &Transport{} ctx := transport.NewServerContext(context.Background(), tr) SetOperation(ctx, "kratos") if !reflect.DeepEqual(tr.operation, "kratos") { t.Errorf("expect %v, got %v", "kratos", tr.operation) } } // TestResponseTransporter_Interface tests that Transport implements ResponseTransporter func TestResponseTransporter_Interface(t *testing.T) { var transport Transporter = &Transport{} if _, ok := transport.(ResponseTransporter); !ok { t.Error("Transport should implement ResponseTransporter interface") } } // TestResponseWriterFromServerContext tests the ResponseWriterFromServerContext helper function func TestResponseWriterFromServerContext(t *testing.T) { tests := []struct { name string setupContext func() context.Context expectWriter bool expectOk bool }{ { name: "valid HTTP transport with ResponseWriter", setupContext: func() context.Context { mockWriter := &mockResponseWriter{header: make(http.Header)} tr := &Transport{response: mockWriter} return transport.NewServerContext(context.Background(), tr) }, expectWriter: true, expectOk: true, }, { name: "context without transport", setupContext: func() context.Context { return context.Background() }, expectWriter: false, expectOk: false, }, { name: "context with non-HTTP transport", setupContext: func() context.Context { tr := &mockNonHTTPTransport{} return transport.NewServerContext(context.Background(), tr) }, expectWriter: false, expectOk: false, }, { name: "context with HTTP transport without ResponseTransporter interface", setupContext: func() context.Context { tr := &mockBasicHTTPTransport{} return transport.NewServerContext(context.Background(), tr) }, expectWriter: false, expectOk: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := tt.setupContext() writer, ok := ResponseWriterFromServerContext(ctx) if ok != tt.expectOk { t.Errorf("ResponseWriterFromServerContext() ok = %v, want %v", ok, tt.expectOk) } if tt.expectWriter && writer == nil { t.Error("ResponseWriterFromServerContext() should return non-nil writer") } if !tt.expectWriter && writer != nil { t.Error("ResponseWriterFromServerContext() should return nil writer") } }) } } // TestTransport_InterfaceCompatibility tests interface compatibility func TestTransport_InterfaceCompatibility(t *testing.T) { tr := &Transport{ endpoint: "http://localhost:8080", operation: "/test", pathTemplate: "/test/{id}", request: &http.Request{}, response: &mockResponseWriter{header: make(http.Header)}, } // Test basic Transporter interface var basicTransporter Transporter = tr if basicTransporter.Request() == nil { t.Error("Transporter.Request() should not be nil") } if basicTransporter.PathTemplate() == "" { t.Error("Transporter.PathTemplate() should not be empty") } // Test ResponseTransporter interface var responseTransporter ResponseTransporter = tr if responseTransporter.Response() == nil { t.Error("ResponseTransporter.Response() should not be nil") } // Test that ResponseTransporter extends Transporter if responseTransporter.Request() == nil { t.Error("ResponseTransporter should have Request() method from Transporter") } } // TestTransport_TypeAssertion tests safe type assertion patterns func TestTransport_TypeAssertion(t *testing.T) { tr := &Transport{ response: &mockResponseWriter{header: make(http.Header)}, } ctx := transport.NewServerContext(context.Background(), tr) // Test type assertion to ResponseTransporter if serverTr, ok := transport.FromServerContext(ctx); ok { if httpTr, ok := serverTr.(ResponseTransporter); ok { if httpTr.Response() == nil { t.Error("ResponseTransporter.Response() should not be nil") } } else { t.Error("Transport should be assertable to ResponseTransporter") } } else { t.Error("Should be able to get transport from server context") } } // Mock implementations for testing // Note: mockResponseWriter is already defined in codec_test.go and will be reused // mockNonHTTPTransport simulates a non-HTTP transport (like gRPC) type mockNonHTTPTransport struct{} func (m *mockNonHTTPTransport) Kind() transport.Kind { return transport.KindGRPC } func (m *mockNonHTTPTransport) Endpoint() string { return "grpc://localhost:9000" } func (m *mockNonHTTPTransport) Operation() string { return "/grpc.Service/Method" } func (m *mockNonHTTPTransport) RequestHeader() transport.Header { return nil } func (m *mockNonHTTPTransport) ReplyHeader() transport.Header { return nil } // mockBasicHTTPTransport simulates an HTTP transport that only implements basic Transporter type mockBasicHTTPTransport struct{} func (m *mockBasicHTTPTransport) Kind() transport.Kind { return transport.KindHTTP } func (m *mockBasicHTTPTransport) Endpoint() string { return "http://localhost:8080" } func (m *mockBasicHTTPTransport) Operation() string { return "/test" } func (m *mockBasicHTTPTransport) RequestHeader() transport.Header { return nil } func (m *mockBasicHTTPTransport) ReplyHeader() transport.Header { return nil } func (m *mockBasicHTTPTransport) Request() *http.Request { return nil } func (m *mockBasicHTTPTransport) PathTemplate() string { return "/test" } ================================================ FILE: transport/transport.go ================================================ package transport import ( "context" "net/url" // init encoding _ "github.com/go-kratos/kratos/v2/encoding/form" _ "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" ) // Server is transport server. type Server interface { Start(context.Context) error Stop(context.Context) error } // Endpointer is registry endpoint. type Endpointer interface { Endpoint() (*url.URL, error) } // Header is the storage medium used by a Header. type Header interface { Get(key string) string Set(key string, value string) Add(key string, value string) Keys() []string Values(key string) []string } // Transporter is transport context value interface. type Transporter interface { // Kind transporter // grpc // http Kind() Kind // Endpoint return server or client endpoint // Server Transport: grpc://127.0.0.1:9000 // Client Transport: discovery:///provider-demo Endpoint() string // Operation Service full method selector generated by protobuf // example: /helloworld.Greeter/SayHello Operation() string // RequestHeader return transport request header // http: http.Header // grpc: metadata.MD RequestHeader() Header // ReplyHeader return transport reply/response header // only valid for server transport // http: http.Header // grpc: metadata.MD ReplyHeader() Header } // Kind defines the type of Transport type Kind string func (k Kind) String() string { return string(k) } // Defines a set of transport kind const ( KindGRPC Kind = "grpc" KindHTTP Kind = "http" ) type ( serverTransportKey struct{} clientTransportKey struct{} ) // NewServerContext returns a new Context that carries value. func NewServerContext(ctx context.Context, tr Transporter) context.Context { return context.WithValue(ctx, serverTransportKey{}, tr) } // FromServerContext returns the Transport value stored in ctx, if any. func FromServerContext(ctx context.Context) (tr Transporter, ok bool) { tr, ok = ctx.Value(serverTransportKey{}).(Transporter) return } // NewClientContext returns a new Context that carries value. func NewClientContext(ctx context.Context, tr Transporter) context.Context { return context.WithValue(ctx, clientTransportKey{}, tr) } // FromClientContext returns the Transport value stored in ctx, if any. func FromClientContext(ctx context.Context) (tr Transporter, ok bool) { tr, ok = ctx.Value(clientTransportKey{}).(Transporter) return } ================================================ FILE: transport/transport_test.go ================================================ package transport import ( "context" "reflect" "testing" ) // mockTransport is a gRPC transport. type mockTransport struct { endpoint string operation string } // Kind returns the transport kind. func (tr *mockTransport) Kind() Kind { return KindGRPC } // Endpoint returns the transport endpoint. func (tr *mockTransport) Endpoint() string { return tr.endpoint } // Operation returns the transport operation. func (tr *mockTransport) Operation() string { return tr.operation } // RequestHeader returns the request header. func (tr *mockTransport) RequestHeader() Header { return nil } // ReplyHeader returns the reply header. func (tr *mockTransport) ReplyHeader() Header { return nil } func TestServerTransport(t *testing.T) { ctx := context.Background() ctx = NewServerContext(ctx, &mockTransport{endpoint: "test_endpoint"}) tr, ok := FromServerContext(ctx) if !ok { t.Errorf("expected:%v got:%v", true, ok) } if tr == nil { t.Errorf("expected:%v got:%v", nil, tr) } mtr, ok := tr.(*mockTransport) if !ok { t.Errorf("expected:%v got:%v", true, ok) } if mtr == nil { t.Fatalf("expected:%v got:%v", nil, mtr) } if mtr.Kind().String() != KindGRPC.String() { t.Errorf("expected:%v got:%v", KindGRPC.String(), mtr.Kind().String()) } if !reflect.DeepEqual(mtr.endpoint, "test_endpoint") { t.Errorf("expected:%v got:%v", "test_endpoint", mtr.endpoint) } } func TestClientTransport(t *testing.T) { ctx := context.Background() ctx = NewClientContext(ctx, &mockTransport{endpoint: "test_endpoint"}) tr, ok := FromClientContext(ctx) if !ok { t.Errorf("expected:%v got:%v", true, ok) } if tr == nil { t.Errorf("expected:%v got:%v", nil, tr) } mtr, ok := tr.(*mockTransport) if !ok { t.Errorf("expected:%v got:%v", true, ok) } if mtr == nil { t.Errorf("expected:%v got:%v", nil, mtr) } if !reflect.DeepEqual(mtr.endpoint, "test_endpoint") { t.Errorf("expected:%v got:%v", "test_endpoint", mtr.endpoint) } } ================================================ FILE: version.go ================================================ package kratos // Release is the current kratos version. const Release = "v2.9.2"