diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.md rename to .gitea/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/feature-request---enhancement.md b/.gitea/ISSUE_TEMPLATE/feature-request---enhancement.md similarity index 100% rename from .github/ISSUE_TEMPLATE/feature-request---enhancement.md rename to .gitea/ISSUE_TEMPLATE/feature-request---enhancement.md diff --git a/.github/ISSUE_TEMPLATE/question.md b/.gitea/ISSUE_TEMPLATE/question.md similarity index 100% rename from .github/ISSUE_TEMPLATE/question.md rename to .gitea/ISSUE_TEMPLATE/question.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE.md rename to .gitea/PULL_REQUEST_TEMPLATE.md diff --git a/.gitea/workflows/job_lint.yml b/.gitea/workflows/job_lint.yml new file mode 100644 index 0000000..d97e747 --- /dev/null +++ b/.gitea/workflows/job_lint.yml @@ -0,0 +1,29 @@ +name: lint + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: + - master + - v3 + - v4 + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v4 + with: + filter: 'blob:none' + - name: setup go + uses: actions/setup-go@v5 + with: + cache-dependency-path: "**/*.sum" + go-version: 'stable' + - name: setup deps + run: go get -v ./... + - name: run lint + uses: https://github.com/golangci/golangci-lint-action@v6 + with: + version: 'latest' diff --git a/.gitea/workflows/job_test.yml b/.gitea/workflows/job_test.yml new file mode 100644 index 0000000..f68cbca --- /dev/null +++ b/.gitea/workflows/job_test.yml @@ -0,0 +1,34 @@ +name: test + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: + - master + - v3 + - v4 + push: + branches: + - master + - v3 + - v4 + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v4 + with: + filter: 'blob:none' + - name: setup go + uses: actions/setup-go@v5 + with: + cache-dependency-path: "**/*.sum" + go-version: 'stable' + - name: setup deps + run: go get -v ./... + - name: run test + env: + INTEGRATION_TESTS: yes + run: go test -mod readonly -v ./... diff --git a/.gitea/workflows/job_tests.yml b/.gitea/workflows/job_tests.yml new file mode 100644 index 0000000..e911576 --- /dev/null +++ b/.gitea/workflows/job_tests.yml @@ -0,0 +1,53 @@ +name: test + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: + - master + - v3 + - v4 + push: + branches: + - master + - v3 + - v4 + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v4 + with: + filter: 'blob:none' + - name: checkout tests + uses: actions/checkout@v4 + with: + ref: master + filter: 'blob:none' + repository: unistack-org/micro-tests + path: micro-tests + - name: setup go + uses: actions/setup-go@v5 + with: + cache-dependency-path: "**/*.sum" + go-version: 'stable' + - name: setup go work + env: + GOWORK: /workspace/${{ github.repository_owner }}/go.work + run: | + go work init + go work use . + go work use micro-tests + - name: setup deps + env: + GOWORK: /workspace/${{ github.repository_owner }}/go.work + run: go get -v ./... + - name: run tests + env: + INTEGRATION_TESTS: yes + GOWORK: /workspace/${{ github.repository_owner }}/go.work + run: | + cd micro-tests + go test -mod readonly -v ./... || true diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index d5f7eae..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,19 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - - # Maintain dependencies for Golang - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/autoapprove.yml b/.github/workflows/autoapprove.yml deleted file mode 100644 index 5bf5d9f..0000000 --- a/.github/workflows/autoapprove.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "autoapprove" - -on: - pull_request_target: - types: [assigned, opened, synchronize, reopened] - -permissions: - pull-requests: write - contents: write - -jobs: - autoapprove: - runs-on: ubuntu-latest - steps: - - name: approve - uses: hmarr/auto-approve-action@v3 - if: github.actor == 'vtolstov' || github.actor == 'dependabot[bot]' - id: approve - with: - github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml deleted file mode 100644 index 5ff3f69..0000000 --- a/.github/workflows/automerge.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: "automerge" - -on: - pull_request_target: - types: [assigned, opened, synchronize, reopened] - -permissions: - pull-requests: write - contents: write - -jobs: - automerge: - runs-on: ubuntu-latest - if: github.actor == 'vtolstov' - steps: - - name: merge - id: merge - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.TOKEN}} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 9603352..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: build -on: - push: - branches: - - master - - v3 -jobs: - test: - name: test - runs-on: ubuntu-latest - steps: - - name: setup - uses: actions/setup-go@v3 - with: - go-version: 1.17 - - name: checkout - uses: actions/checkout@v3 - - name: cache - uses: actions/cache@v3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: deps - run: go get -v -t -d ./... - - name: test - env: - INTEGRATION_TESTS: yes - run: go test -mod readonly -v ./... - lint: - name: lint - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - - name: lint - uses: golangci/golangci-lint-action@v3.4.0 - continue-on-error: true - with: - # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.30 - # Optional: working directory, useful for monorepos - # working-directory: somedir - # Optional: golangci-lint command line arguments. - # args: --issues-exit-code=0 - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 2f6c6de..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,78 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "codeql" - -on: - workflow_run: - workflows: ["prbuild"] - types: - - completed - push: - branches: [ master, v3 ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master, v3 ] - schedule: - - cron: '34 1 * * 0' - -jobs: - analyze: - name: analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: checkout - uses: actions/checkout@v3 - - name: setup - uses: actions/setup-go@v3 - with: - go-version: 1.17 - # Initializes the CodeQL tools for scanning. - - name: init - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: autobuild - uses: github/codeql-action/autobuild@v2 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: analyze - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml deleted file mode 100644 index f41c1c0..0000000 --- a/.github/workflows/dependabot-automerge.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "dependabot-automerge" - -on: - pull_request_target: - types: [assigned, opened, synchronize, reopened] - -permissions: - pull-requests: write - contents: write - -jobs: - automerge: - runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' - steps: - - name: metadata - id: metadata - uses: dependabot/fetch-metadata@v1.3.6 - with: - github-token: "${{ secrets.TOKEN }}" - - name: merge - id: merge - if: ${{contains(steps.metadata.outputs.dependency-names, 'go.unistack.org')}} - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.TOKEN}} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml deleted file mode 100644 index f313ebe..0000000 --- a/.github/workflows/pr.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: prbuild -on: - pull_request: - branches: - - master - - v3 -jobs: - test: - name: test - runs-on: ubuntu-latest - steps: - - name: setup - uses: actions/setup-go@v3 - with: - go-version: 1.17 - - name: checkout - uses: actions/checkout@v3 - - name: cache - uses: actions/cache@v3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: deps - run: go get -v -t -d ./... - - name: test - env: - INTEGRATION_TESTS: yes - run: go test -mod readonly -v ./... - lint: - name: lint - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - - name: lint - uses: golangci/golangci-lint-action@v3.4.0 - continue-on-error: true - with: - # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.30 - # Optional: working directory, useful for monorepos - # working-directory: somedir - # Optional: golangci-lint command line arguments. - # args: --issues-exit-code=0 - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2fff38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Develop tools +/.vscode/ +/.idea/ +.idea +.vscode + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Folders +_obj +_test +_build +.DS_Store + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# vim temp files +*~ +*.swp +*.swo diff --git a/.golangci.yml b/.golangci.yml index 6ff842d..2bb1c30 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,44 +1,5 @@ run: - concurrency: 4 + concurrency: 8 deadline: 5m issues-exit-code: 1 tests: true - -linters-settings: - govet: - check-shadowing: true - enable: - - fieldalignment - -linters: - enable: - - govet - - deadcode - - errcheck - - govet - - ineffassign - - staticcheck - - structcheck - - typecheck - - unused - - varcheck - - bodyclose - - gci - - goconst - - gocritic - - gosimple - - gofmt - - gofumpt - - goimports - - golint - - gosec - - makezero - - misspell - - nakedret - - nestif - - nilerr - - noctx - - prealloc - - unconvert - - unparam - disable-all: false diff --git a/README.md b/README.md index 564d4bd..2b23578 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ This plugin is a http client for micro. ## Overview The http client wraps `net/http` to provide a robust micro client with service discovery, load balancing and streaming. -It complies with the [micro.Client](https://godoc.org/go.unistack.org/micro-client-http/v3#Client) interface. +It complies with the [micro.Client](https://godoc.org/go.unistack.org/micro-client-http/v4#Client) interface. ## Usage ### Use directly ```go -import "go.unistack.org/micro-client-http/v3" +import "go.unistack.org/micro-client-http/v4" service := micro.NewService( micro.Name("my.service"), diff --git a/go.mod b/go.mod index 4f0bd11..aa7a15f 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,18 @@ -module go.unistack.org/micro-client-http/v3 +module go.unistack.org/micro-client-http/v4 -go 1.18 +go 1.22.0 -require go.unistack.org/micro/v3 v3.10.16 +require go.unistack.org/micro/v4 v4.1.2 + +require ( + github.com/ash3in/uuidv8 v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/matoous/go-nanoid v1.5.1 // indirect + github.com/spf13/cast v1.7.1 // indirect + go.unistack.org/micro-proto/v4 v4.1.0 // indirect + golang.org/x/sys v0.29.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index af3467d..c7c5c14 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,43 @@ -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/silas/dag v0.0.0-20211117232152-9d50aa809f35/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= -go.unistack.org/micro/v3 v3.10.16 h1:2er/SKKYbV60M+UuJM4eYCF0MZYAIq/yNUrAbTfgq8Q= -go.unistack.org/micro/v3 v3.10.16/go.mod h1:uMAc0U/x7dmtICCrblGf0ZLgYegu3VwQAquu+OFCw1Q= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/ash3in/uuidv8 v1.2.0 h1:2oogGdtCPwaVtyvPPGin4TfZLtOGE5F+W++E880G6SI= +github.com/ash3in/uuidv8 v1.2.0/go.mod h1:BnU0wJBxnzdEKmVg4xckBkD+VZuecTFTUP3M0dWgyY4= +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/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.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/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4= +github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +go.unistack.org/micro-proto/v4 v4.1.0 h1:qPwL2n/oqh9RE3RTTDgt28XK3QzV597VugQPaw9lKUk= +go.unistack.org/micro-proto/v4 v4.1.0/go.mod h1:ArmK7o+uFvxSY3dbJhKBBX4Pm1rhWdLEFf3LxBrMtec= +go.unistack.org/micro/v4 v4.1.2 h1:9SOlPYyPNNFpg1A7BsvhDyQm3gysLH1AhWbDCp1hyoY= +go.unistack.org/micro/v4 v4.1.2/go.mod h1:lr3oYED8Ay1vjK68QqRw30QOtdk/ffpZqMFDasOUhKw= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= diff --git a/http.go b/http.go index 22150b8..d2952ca 100644 --- a/http.go +++ b/http.go @@ -1,28 +1,27 @@ // Package http provides a http client -package http // import "go.unistack.org/micro-client-http/v3" +package http // import "go.unistack.org/micro-client-http/v4" import ( "bufio" "bytes" "context" "fmt" - "io/ioutil" + "io" "net" "net/http" "net/url" - "os" "strings" "sync" "time" - "go.unistack.org/micro/v3/broker" - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/codec" - "go.unistack.org/micro/v3/errors" - "go.unistack.org/micro/v3/logger" - "go.unistack.org/micro/v3/metadata" - "go.unistack.org/micro/v3/selector" - rutil "go.unistack.org/micro/v3/util/reflect" + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/codec" + "go.unistack.org/micro/v4/errors" + "go.unistack.org/micro/v4/logger" + "go.unistack.org/micro/v4/metadata" + "go.unistack.org/micro/v4/options" + "go.unistack.org/micro/v4/selector" + rutil "go.unistack.org/micro/v4/util/reflect" ) var DefaultContentType = "application/json" @@ -116,7 +115,7 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. u, err = u.Parse(path) if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } var nmsg interface{} @@ -127,12 +126,12 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. } if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } u, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, path)) if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } var cookies []*http.Cookie @@ -140,17 +139,22 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. if opts.Context != nil { if md, ok := opts.Context.Value(metadataKey{}).(metadata.Metadata); ok { for k, v := range md { - header.Set(k, v) + header[k] = v } } } if opts.AuthToken != "" { header.Set(metadata.HeaderAuthorization, opts.AuthToken) } + if opts.RequestMetadata != nil { + for k, v := range opts.RequestMetadata { + header[k] = v + } + } if md, ok := metadata.FromOutgoingContext(ctx); ok { for k, v := range md { - header.Set(k, v) + header[k] = v } } @@ -170,11 +174,11 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. for k, required := range vm { v, err = rutil.StructFieldByPath(msg, k) if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } if rutil.IsZero(v) { if required == "true" { - return nil, errors.BadRequest("go.micro.client", fmt.Sprintf("required field %s not set", k)) + return nil, errors.BadRequest("go.micro.client", "required field %s not set", k) } continue } @@ -190,12 +194,12 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. b, err := cf.Marshal(nmsg) if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } var hreq *http.Request if len(b) > 0 { - hreq, err = http.NewRequestWithContext(ctx, method, u.String(), ioutil.NopCloser(bytes.NewBuffer(b))) + hreq, err = http.NewRequestWithContext(ctx, method, u.String(), io.NopCloser(bytes.NewBuffer(b))) hreq.ContentLength = int64(len(b)) header.Set("Content-Length", fmt.Sprintf("%d", hreq.ContentLength)) } else { @@ -203,7 +207,7 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. } if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } hreq.Header = header @@ -212,7 +216,7 @@ func newRequest(ctx context.Context, log logger.Logger, addr string, req client. } if log.V(logger.DebugLevel) { - log.Debugf(ctx, "request %s to %s with headers %v body %s", method, u.String(), hreq.Header, b) + log.Debug(ctx, fmt.Sprintf("request %s to %s with headers %v body %s", method, u.String(), hreq.Header, b)) } return hreq, nil @@ -226,7 +230,7 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request, cf, err := h.newCodec(ct) if err != nil { - return errors.BadRequest("go.micro.client", err.Error()) + return errors.BadRequest("go.micro.client", "%+v", err) } hreq, err := newRequest(ctx, h.opts.Logger, addr, req, ct, cf, req.Body(), opts) if err != nil { @@ -239,14 +243,14 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request, switch err := err.(type) { case *url.Error: if err, ok := err.Err.(net.Error); ok && err.Timeout() { - return errors.Timeout("go.micro.client", err.Error()) + return errors.Timeout("go.micro.client", "%+v", err) } case net.Error: if err.Timeout() { - return errors.Timeout("go.micro.client", err.Error()) + return errors.Timeout("go.micro.client", "%+v", err) } } - return errors.InternalServerError("go.micro.client", err.Error()) + return errors.InternalServerError("go.micro.client", "%+v", err) } defer hrsp.Body.Close() @@ -263,12 +267,12 @@ func (h *httpClient) stream(ctx context.Context, addr string, req client.Request // get codec cf, err := h.newCodec(ct) if err != nil { - return nil, errors.BadRequest("go.micro.client", err.Error()) + return nil, errors.BadRequest("go.micro.client", "%+v", err) } cc, err := (h.httpcli.Transport).(*http.Transport).DialContext(ctx, "tcp", addr) if err != nil { - return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error dialing: %v", err)) + return nil, errors.InternalServerError("go.micro.client", "Error dialing: %v", err) } return &httpStream{ @@ -308,9 +312,6 @@ func (h *httpClient) Init(opts ...client.Option) error { o(&h.opts) } - if err := h.opts.Broker.Init(); err != nil { - return err - } if err := h.opts.Tracer.Init(); err != nil { return err } @@ -323,9 +324,6 @@ func (h *httpClient) Init(opts ...client.Option) error { if err := h.opts.Meter.Init(); err != nil { return err } - if err := h.opts.Transport.Init(); err != nil { - return err - } return nil } @@ -334,10 +332,6 @@ func (h *httpClient) Options() client.Options { return h.opts } -func (h *httpClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message { - return newHTTPMessage(topic, msg, h.opts.ContentType, opts...) -} - func (h *httpClient) NewRequest(service, method string, req interface{}, opts ...client.RequestOption) client.Request { return newHTTPRequest(service, method, req, h.opts.ContentType, opts...) } @@ -359,8 +353,10 @@ func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface } else { // got a deadline so no need to setup context // but we need to set the timeout we pass along - opt := client.WithRequestTimeout(time.Until(d)) - opt(&callOpts) + o := options.NewOption("RequestTimeout")(time.Until(d)) + if err := options.Apply(&callOpts, o); err != nil { + return errors.New("go.micro.client", fmt.Sprintf("%v", err.Error()), 400) + } } // should we noop right here? @@ -374,9 +370,9 @@ func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface hcall := h.call // wrap the call in reverse - for i := len(callOpts.CallWrappers); i > 0; i-- { - hcall = callOpts.CallWrappers[i-1](hcall) - } + //for i := len(callOpts.CallWrappers); i > 0; i-- { + // hcall = callOpts.CallWrappers[i-1](hcall) + //} // use the router passed as a call option, or fallback to the rpc clients router if callOpts.Router == nil { @@ -400,7 +396,7 @@ func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface // call backoff first. Someone may want an initial start delay t, err := callOpts.Backoff(ctx, req, i) if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + return errors.InternalServerError("go.micro.client", "%+v", err) } // only sleep if greater than 0 @@ -414,7 +410,7 @@ func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface // TODO apply any filtering here routes, err = h.opts.Lookup(ctx, req, callOpts) if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + return errors.InternalServerError("go.micro.client", "%+v", err) } // balance the list of nodes @@ -493,8 +489,10 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli } else { // got a deadline so no need to setup context // but we need to set the timeout we pass along - o := client.WithStreamTimeout(time.Until(d)) - o(&callOpts) + o := options.NewOption("StreamTimeout")(time.Until(d)) + if err := options.Apply(&callOpts, o); err != nil { + return nil, errors.New("go.micro.client", fmt.Sprintf("%v", err.Error()), 400) + } } // should we noop right here? @@ -534,7 +532,7 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli // call backoff first. Someone may want an initial start delay t, cerr := callOpts.Backoff(ctx, req, i) if cerr != nil { - return nil, errors.InternalServerError("go.micro.client", cerr.Error()) + return nil, errors.InternalServerError("go.micro.client", "%+v", cerr) } // only sleep if greater than 0 @@ -548,7 +546,7 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli // TODO apply any filtering here routes, err = h.opts.Lookup(ctx, req, callOpts) if err != nil { - return nil, errors.InternalServerError("go.micro.client", err.Error()) + return nil, errors.InternalServerError("go.micro.client", "%+v", err) } // balance the list of nodes @@ -614,71 +612,6 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli return nil, grr } -func (h *httpClient) BatchPublish(ctx context.Context, p []client.Message, opts ...client.PublishOption) error { - return h.publish(ctx, p, opts...) -} - -func (h *httpClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { - return h.publish(ctx, []client.Message{p}, opts...) -} - -func (h *httpClient) publish(ctx context.Context, ps []client.Message, opts ...client.PublishOption) error { - var body []byte - - options := client.NewPublishOptions(opts...) - - // get proxy - exchange := "" - if v, ok := os.LookupEnv("MICRO_PROXY"); ok { - exchange = v - } - - omd, ok := metadata.FromOutgoingContext(ctx) - if !ok { - omd = metadata.New(2) - } - - msgs := make([]*broker.Message, 0, len(ps)) - - for _, p := range ps { - md := metadata.Copy(omd) - md[metadata.HeaderContentType] = p.ContentType() - - // passed in raw data - if d, ok := p.Payload().(*codec.Frame); ok { - body = d.Data - } else { - // use codec for payload - cf, err := h.newCodec(p.ContentType()) - if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) - } - // set the body - b, err := cf.Marshal(p.Payload()) - if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) - } - body = b - } - - topic := p.Topic() - if len(exchange) > 0 { - topic = exchange - } - - for k, v := range p.Metadata() { - md.Set(k, v) - } - md.Set(metadata.HeaderTopic, topic) - msgs = append(msgs, &broker.Message{Header: md, Body: body}) - } - - return h.opts.Broker.BatchPublish(ctx, msgs, - broker.PublishContext(ctx), - broker.PublishBodyOnly(options.BodyOnly), - ) -} - func (h *httpClient) String() string { return "http" } @@ -735,12 +668,6 @@ func NewClient(opts ...client.Option) client.Client { } rc.httpcli = &http.Client{Transport: tr} } - c := client.Client(rc) - // wrap in reverse - for i := len(options.Wrappers); i > 0; i-- { - c = options.Wrappers[i-1](c) - } - - return c + return rc } diff --git a/message.go b/message.go deleted file mode 100644 index 5b7f95a..0000000 --- a/message.go +++ /dev/null @@ -1,44 +0,0 @@ -package http - -import ( - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/metadata" -) - -type httpMessage struct { - payload interface{} - topic string - contentType string - opts client.MessageOptions -} - -func newHTTPMessage(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message { - options := client.NewMessageOptions(opts...) - - if len(options.ContentType) > 0 { - contentType = options.ContentType - } - - return &httpMessage{ - payload: payload, - topic: topic, - contentType: contentType, - opts: options, - } -} - -func (h *httpMessage) ContentType() string { - return h.contentType -} - -func (h *httpMessage) Topic() string { - return h.topic -} - -func (h *httpMessage) Payload() interface{} { - return h.payload -} - -func (h *httpMessage) Metadata() metadata.Metadata { - return h.opts.Metadata -} diff --git a/options.go b/options.go index 0e24695..5772a21 100644 --- a/options.go +++ b/options.go @@ -1,11 +1,12 @@ package http import ( + "context" "net" "net/http" - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/metadata" + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/metadata" ) var ( @@ -30,28 +31,48 @@ type poolMaxStreams struct{} // PoolMaxStreams maximum streams on a connectioin func PoolMaxStreams(n int) client.Option { - return client.SetOption(poolMaxStreams{}, n) + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, poolMaxStreams{}, n) + } } type poolMaxIdle struct{} // PoolMaxIdle maximum idle conns of a pool func PoolMaxIdle(d int) client.Option { - return client.SetOption(poolMaxIdle{}, d) + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, poolMaxIdle{}, d) + } } type maxRecvMsgSizeKey struct{} // MaxRecvMsgSize set the maximum size of message that client can receive. func MaxRecvMsgSize(s int) client.Option { - return client.SetOption(maxRecvMsgSizeKey{}, s) + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, maxRecvMsgSizeKey{}, s) + } } type maxSendMsgSizeKey struct{} // MaxSendMsgSize set the maximum size of message that client can send. func MaxSendMsgSize(s int) client.Option { - return client.SetOption(maxSendMsgSizeKey{}, s) + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, maxSendMsgSizeKey{}, s) + } } type httpClientKey struct{} @@ -59,7 +80,12 @@ type httpClientKey struct{} // nolint: golint // HTTPClient pass http.Client option to client Call func HTTPClient(c *http.Client) client.Option { - return client.SetOption(httpClientKey{}, c) + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, httpClientKey{}, c) + } } type httpDialerKey struct{} @@ -67,60 +93,105 @@ type httpDialerKey struct{} // nolint: golint // HTTPDialer pass net.Dialer option to client func HTTPDialer(d *net.Dialer) client.Option { - return client.SetOption(httpDialerKey{}, d) + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, httpDialerKey{}, d) + } } type methodKey struct{} // Method pass method option to client Call func Method(m string) client.CallOption { - return client.SetCallOption(methodKey{}, m) + return func(o *client.CallOptions) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, methodKey{}, m) + } } type pathKey struct{} // Path spcecifies path option to client Call -func Path(p string) client.CallOption { - return client.SetCallOption(pathKey{}, p) +func Path(p string) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, pathKey{}, p) + } } type bodyKey struct{} // Body specifies body option to client Call -func Body(b string) client.CallOption { - return client.SetCallOption(bodyKey{}, b) +func Body(b string) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, bodyKey{}, b) + } } type errorMapKey struct{} -func ErrorMap(m map[string]interface{}) client.CallOption { - return client.SetCallOption(errorMapKey{}, m) +func ErrorMap(m map[string]interface{}) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, errorMapKey{}, m) + } } type structTagsKey struct{} // StructTags pass tags slice option to client Call -func StructTags(tags []string) client.CallOption { - return client.SetCallOption(structTagsKey{}, tags) +func StructTags(tags []string) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, structTagsKey{}, tags) + } } type metadataKey struct{} // Metadata pass metadata to client Call -func Metadata(md metadata.Metadata) client.CallOption { - return client.SetCallOption(metadataKey{}, md) +func Metadata(md metadata.Metadata) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, metadataKey{}, md) + } } type cookieKey struct{} // Cookie pass cookie to client Call -func Cookie(cookies ...string) client.CallOption { - return client.SetCallOption(cookieKey{}, cookies) +func Cookie(cookies ...string) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, cookieKey{}, cookies) + } } type headerKey struct{} // Header pass cookie to client Call -func Header(headers ...string) client.CallOption { - return client.SetCallOption(headerKey{}, headers) +func Header(headers ...string) client.Option { + return func(o *client.Options) { + if o.Context == nil { + o.Context = context.Background() + } + o.Context = context.WithValue(o.Context, headerKey{}, headers) + } } diff --git a/request.go b/request.go index 00c23a4..0622422 100644 --- a/request.go +++ b/request.go @@ -1,8 +1,8 @@ package http import ( - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/codec" + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/codec" ) type httpRequest struct { diff --git a/stream.go b/stream.go index 7c70b50..9f30103 100644 --- a/stream.go +++ b/stream.go @@ -9,10 +9,10 @@ import ( "net/http" "sync" - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/codec" - "go.unistack.org/micro/v3/errors" - "go.unistack.org/micro/v3/logger" + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/codec" + "go.unistack.org/micro/v4/errors" + "go.unistack.org/micro/v4/logger" ) // Implements the streamer interface @@ -90,7 +90,7 @@ func (h *httpStream) Recv(msg interface{}) error { hrsp, err := http.ReadResponse(h.reader, new(http.Request)) if err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + return errors.InternalServerError("go.micro.client", "%+v", err) } defer hrsp.Body.Close() @@ -134,19 +134,19 @@ func (h *httpStream) parseRsp(ctx context.Context, log logger.Logger, hrsp *http buf, err = io.ReadAll(hrsp.Body) if err != nil { if log.V(logger.ErrorLevel) { - log.Errorf(ctx, "failed to read body: %v", err) + log.Error(ctx, "failed to read body", err) } - return errors.InternalServerError("go.micro.client", string(buf)) + return errors.InternalServerError("go.micro.client", "%s", buf) } } if log.V(logger.DebugLevel) { - log.Debugf(ctx, "response %s with %v", buf, hrsp.Header) + log.Debug(ctx, fmt.Sprintf("response %s with %v", buf, hrsp.Header)) } if hrsp.StatusCode < 400 { if err = cf.Unmarshal(buf, rsp); err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + return errors.InternalServerError("go.micro.client", "%+v", err) } return nil } @@ -163,7 +163,7 @@ func (h *httpStream) parseRsp(ctx context.Context, log logger.Logger, hrsp *http } if cerr := cf.Unmarshal(buf, rerr); cerr != nil { - return errors.InternalServerError("go.micro.client", cerr.Error()) + return errors.InternalServerError("go.micro.client", "%+v", cerr) } if err, ok = rerr.(error); !ok { diff --git a/util.go b/util.go index f5fcf36..d463185 100644 --- a/util.go +++ b/util.go @@ -10,10 +10,11 @@ import ( "strings" "sync" - "go.unistack.org/micro/v3/client" - "go.unistack.org/micro/v3/errors" - "go.unistack.org/micro/v3/logger" - rutil "go.unistack.org/micro/v3/util/reflect" + "go.unistack.org/micro/v4/client" + "go.unistack.org/micro/v4/errors" + "go.unistack.org/micro/v4/logger" + "go.unistack.org/micro/v4/metadata" + rutil "go.unistack.org/micro/v4/util/reflect" ) var ( @@ -216,13 +217,21 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta _, _ = b.WriteString(values.Encode()) } - if rutil.IsZero(nmsg) { + if rutil.IsZero(nmsg) && !isEmptyStruct(nmsg) { return b.String(), nil, nil } return b.String(), nmsg, nil } +func isEmptyStruct(v interface{}) bool { + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + return val.Kind() == reflect.Struct && val.NumField() == 0 +} + func newTemplate(path string) ([]string, error) { if len(path) == 0 || path[0] != '/' { return nil, fmt.Errorf("path must starts with /") @@ -252,6 +261,13 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte return nil } + if opts.ResponseMetadata != nil { + *opts.ResponseMetadata = metadata.New(len(hrsp.Header)) + for k, v := range hrsp.Header { + opts.ResponseMetadata.Set(k, strings.Join(v, ",")) + } + } + select { case <-ctx.Done(): err = ctx.Err() @@ -266,28 +282,28 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte buf, err = io.ReadAll(hrsp.Body) if err != nil { if h.opts.Logger.V(logger.ErrorLevel) { - h.opts.Logger.Errorf(ctx, "failed to read body: %v", err) + h.opts.Logger.Error(ctx, "failed to read body", err) } - return errors.InternalServerError("go.micro.client", string(buf)) + return errors.InternalServerError("go.micro.client", "%s", buf) } } cf, cerr := h.newCodec(ct) if cerr != nil { if h.opts.Logger.V(logger.DebugLevel) { - h.opts.Logger.Debugf(ctx, "response with %v unknown content-type %s", hrsp.Header, ct, buf) + h.opts.Logger.Debug(ctx, fmt.Sprintf("response with %v unknown content-type %s %s", hrsp.Header, ct, buf)) } - return errors.InternalServerError("go.micro.client", cerr.Error()) + return errors.InternalServerError("go.micro.client", "%+v", cerr) } if h.opts.Logger.V(logger.DebugLevel) { - h.opts.Logger.Debugf(ctx, "response %s with %v", buf, hrsp.Header) + h.opts.Logger.Debug(ctx, fmt.Sprintf("response %s with %v", buf, hrsp.Header)) } // succeseful response if hrsp.StatusCode < 400 { if err = cf.Unmarshal(buf, rsp); err != nil { - return errors.InternalServerError("go.micro.client", err.Error()) + return errors.InternalServerError("go.micro.client", "%+v", err) } return nil } @@ -307,7 +323,7 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte } if cerr := cf.Unmarshal(buf, rerr); cerr != nil { - return errors.InternalServerError("go.micro.client", cerr.Error()) + return errors.InternalServerError("go.micro.client", "%+v", cerr) } if err, ok = rerr.(error); !ok { diff --git a/util_test.go b/util_test.go index 5d8b799..1c0b713 100644 --- a/util_test.go +++ b/util_test.go @@ -59,6 +59,38 @@ func TestNewPathRequest(t *testing.T) { } } +func TestNewPathRequestWithEmptyBody(t *testing.T) { + val := struct{}{} + cases := []string{ + "", + "*", + "{}", + "nil", + `{"type": "invalid"}`, + } + + for _, body := range cases { + for _, m := range []string{"POST", "PUT", "PATCH", "GET", "DELETE"} { + path, nmsg, err := newPathRequest("/v1/test", m, body, val, []string{"protobuf", "json"}, nil) + if err != nil { + t.Fatal(err) + } + if nmsg == nil { + t.Fatalf("invalid path: nil nmsg") + } + + u, err := url.Parse(path) + if err != nil { + t.Fatal(err) + } + vals := u.Query() + if len(vals) != 0 { + t.Fatalf("invalid path: %v nmsg: %v", path, nmsg) + } + } + } +} + func TestNewPathVarRequest(t *testing.T) { type Message struct { Name string `json:"name"`