├── .editorconfig ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── dependabot.yaml └── workflows │ ├── auto-merge.yml │ └── go-test.yml ├── .gitignore ├── .golangci.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── authinfo_test.go ├── bytestream.go ├── bytestream_test.go ├── client ├── auth_info.go ├── auth_info_test.go ├── keepalive.go ├── keepalive_test.go ├── opentelemetry.go ├── opentelemetry_test.go ├── opentracing.go ├── opentracing_test.go ├── request.go ├── request_test.go ├── response.go ├── response_test.go ├── runtime.go ├── runtime_test.go └── runtime_tls_test.go ├── client_auth_info.go ├── client_operation.go ├── client_request.go ├── client_request_test.go ├── client_response.go ├── client_response_test.go ├── constants.go ├── csv.go ├── csv_options.go ├── csv_test.go ├── discard.go ├── file.go ├── file_test.go ├── fixtures ├── bugs │ ├── 172 │ │ └── swagger.yml │ ├── 174 │ │ └── swagger.yml │ └── 264 │ │ └── swagger.yml └── certs │ ├── myCA.crt │ ├── myCA.key │ ├── mycert1.crt │ ├── mycert1.key │ ├── mycert1.req │ ├── myclient-ecc.crt │ ├── myclient-ecc.csr │ ├── myclient-ecc.key │ ├── myclient.crt │ ├── myclient.csr │ ├── myclient.key │ ├── myclient.p12 │ └── serial ├── flagext ├── byte_size.go └── byte_size_test.go ├── go.mod ├── go.sum ├── hack └── gen-self-signed-certs.sh ├── headers.go ├── headers_test.go ├── interfaces.go ├── internal └── testing │ ├── data.go │ ├── data_test.go │ ├── petstore │ ├── api.go │ └── api_test.go │ └── simplepetstore │ ├── api.go │ └── api_test.go ├── json.go ├── json_test.go ├── logger ├── logger.go ├── logger_test.go └── standard.go ├── middleware ├── body_test.go ├── context.go ├── context_test.go ├── debug_test.go ├── denco │ ├── LICENSE │ ├── README.md │ ├── router.go │ ├── router_bench_test.go │ ├── router_test.go │ ├── server.go │ ├── server_test.go │ ├── util.go │ └── util_test.go ├── doc.go ├── header │ ├── header.go │ └── header_test.go ├── negotiate.go ├── negotiate_test.go ├── not_implemented.go ├── not_implemented_test.go ├── operation.go ├── operation_test.go ├── parameter.go ├── parameter_test.go ├── rapidoc.go ├── rapidoc_test.go ├── redoc.go ├── redoc_test.go ├── request.go ├── request_test.go ├── route_authenticator_test.go ├── route_param_test.go ├── router.go ├── router_test.go ├── security.go ├── security_test.go ├── spec.go ├── spec_test.go ├── string_conversion_test.go ├── swaggerui.go ├── swaggerui_oauth2.go ├── swaggerui_oauth2_test.go ├── swaggerui_test.go ├── ui_options.go ├── ui_options_test.go ├── untyped │ ├── api.go │ └── api_test.go ├── untyped_request_test.go ├── validation.go └── validation_test.go ├── request.go ├── request_test.go ├── security ├── apikey_auth_test.go ├── authenticator.go ├── authorizer.go ├── authorizer_test.go ├── basic_auth_test.go └── bearer_auth_test.go ├── statuses.go ├── text.go ├── text_test.go ├── values.go ├── values_test.go ├── xml.go ├── xml_test.go └── yamlpc ├── yaml.go └── yaml_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | 12 | # Set default charset 13 | [*.{js,py,go,scala,rb,java,html,css,less,sass,md}] 14 | charset = utf-8 15 | 16 | # Tab indentation (no size specified) 17 | [*.go] 18 | indent_style = tab 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | # Matches the exact files either package.json or .travis.yml 24 | [{package.json,.travis.yml}] 25 | indent_style = space 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribution Guidelines 2 | 3 | ### Pull requests are always welcome 4 | 5 | We are always thrilled to receive pull requests, and do our best to 6 | process them as fast as possible. Not sure if that typo is worth a pull 7 | request? Do it! We will appreciate it. 8 | 9 | If your pull request is not accepted on the first try, don't be 10 | discouraged! If there's a problem with the implementation, hopefully you 11 | received feedback on what to improve. 12 | 13 | We're trying very hard to keep go-swagger lean and focused. We don't want it 14 | to do everything for everybody. This means that we might decide against 15 | incorporating a new feature. However, there might be a way to implement 16 | that feature *on top of* go-swagger. 17 | 18 | 19 | ### Conventions 20 | 21 | Fork the repo and make changes on your fork in a feature branch: 22 | 23 | - If it's a bugfix branch, name it XXX-something where XXX is the number of the 24 | issue 25 | - If it's a feature branch, create an enhancement issue to announce your 26 | intentions, and name it XXX-something where XXX is the number of the issue. 27 | 28 | Submit unit tests for your changes. Go has a great test framework built in; use 29 | it! Take a look at existing tests for inspiration. Run the full test suite on 30 | your branch before submitting a pull request. 31 | 32 | Update the documentation when creating or modifying features. Test 33 | your documentation changes for clarity, concision, and correctness, as 34 | well as a clean documentation build. See ``docs/README.md`` for more 35 | information on building the docs and how docs get released. 36 | 37 | Write clean code. Universally formatted code promotes ease of writing, reading, 38 | and maintenance. Always run `gofmt -s -w file.go` on each changed file before 39 | committing your changes. Most editors have plugins that do this automatically. 40 | 41 | Pull requests descriptions should be as clear as possible and include a 42 | reference to all the issues that they address. 43 | 44 | Pull requests must not contain commits from other users or branches. 45 | 46 | Commit messages must start with a capitalized and short summary (max. 50 47 | chars) written in the imperative, followed by an optional, more detailed 48 | explanatory text which is separated from the summary by an empty line. 49 | 50 | Code review comments may be added to your pull request. Discuss, then make the 51 | suggested modifications and push additional commits to your feature branch. Be 52 | sure to post a comment after pushing. The new commits will show up in the pull 53 | request automatically, but the reviewers will not be notified unless you 54 | comment. 55 | 56 | Before the pull request is merged, make sure that you squash your commits into 57 | logical units of work using `git rebase -i` and `git push -f`. After every 58 | commit the test suite should be passing. Include documentation changes in the 59 | same commit so that a revert would remove all traces of the feature or fix. 60 | 61 | Commits that fix or close an issue should include a reference like `Closes #XXX` 62 | or `Fixes #XXX`, which will automatically close the issue when merged. 63 | 64 | ### Sign your work 65 | 66 | The sign-off is a simple line at the end of the explanation for the 67 | patch, which certifies that you wrote it or otherwise have the right to 68 | pass it on as an open-source patch. The rules are pretty simple: if you 69 | can certify the below (from 70 | [developercertificate.org](http://developercertificate.org/)): 71 | 72 | ``` 73 | Developer Certificate of Origin 74 | Version 1.1 75 | 76 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 77 | 660 York Street, Suite 102, 78 | San Francisco, CA 94110 USA 79 | 80 | Everyone is permitted to copy and distribute verbatim copies of this 81 | license document, but changing it is not allowed. 82 | 83 | 84 | Developer's Certificate of Origin 1.1 85 | 86 | By making a contribution to this project, I certify that: 87 | 88 | (a) The contribution was created in whole or in part by me and I 89 | have the right to submit it under the open source license 90 | indicated in the file; or 91 | 92 | (b) The contribution is based upon previous work that, to the best 93 | of my knowledge, is covered under an appropriate open source 94 | license and I have the right under that license to submit that 95 | work with modifications, whether created in whole or in part 96 | by me, under the same open source license (unless I am 97 | permitted to submit under a different license), as indicated 98 | in the file; or 99 | 100 | (c) The contribution was provided directly to me by some other 101 | person who certified (a), (b) or (c) and I have not modified 102 | it. 103 | 104 | (d) I understand and agree that this project and the contribution 105 | are public and that a record of the contribution (including all 106 | personal information I submit with it, including my sign-off) is 107 | maintained indefinitely and may be redistributed consistent with 108 | this project or the open source license(s) involved. 109 | ``` 110 | 111 | then you just add a line to every git commit message: 112 | 113 | Signed-off-by: Joe Smith 114 | 115 | using your real name (sorry, no pseudonyms or anonymous contributions.) 116 | 117 | You can add the sign off when creating the git commit via `git commit -s`. 118 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "friday" 8 | open-pull-requests-limit: 2 # <- default is 5 9 | groups: # <- group all github actions updates in a single PR 10 | # 1. development-dependencies are auto-merged 11 | development-dependencies: 12 | patterns: 13 | - '*' 14 | 15 | - package-ecosystem: "gomod" 16 | # We define 4 groups of dependencies to regroup update pull requests: 17 | # - development (e.g. test dependencies) 18 | # - go-openapi updates 19 | # - golang.org (e.g. golang.org/x/... packages) 20 | # - other dependencies (direct or indirect) 21 | # 22 | # * All groups are checked once a week and each produce at most 1 PR. 23 | # * All dependabot PRs are auto-approved 24 | # 25 | # Auto-merging policy, when requirements are met: 26 | # 1. development-dependencies are auto-merged 27 | # 2. golang.org-dependencies are auto-merged 28 | # 3. go-openapi patch updates are auto-merged. Minor/major version updates require a manual merge. 29 | # 4. other dependencies require a manual merge 30 | directory: "/" 31 | schedule: 32 | interval: "weekly" 33 | day: "friday" 34 | open-pull-requests-limit: 4 35 | groups: 36 | development-dependencies: 37 | patterns: 38 | - "github.com/stretchr/testify" 39 | 40 | golang.org-dependencies: 41 | patterns: 42 | - "golang.org/*" 43 | 44 | go-openapi-dependencies: 45 | patterns: 46 | - "github.com/go-openapi/*" 47 | 48 | other-dependencies: 49 | exclude-patterns: 50 | - "github.com/go-openapi/*" 51 | - "github.com/stretchr/testify" 52 | - "golang.org/*" 53 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: github.event.pull_request.user.login == 'dependabot[bot]' 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2 16 | 17 | - name: Auto-approve all dependabot PRs 18 | run: gh pr review --approve "$PR_URL" 19 | env: 20 | PR_URL: ${{github.event.pull_request.html_url}} 21 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 22 | 23 | - name: Auto-merge dependabot PRs for development dependencies 24 | if: contains(steps.metadata.outputs.dependency-group, 'development-dependencies') 25 | run: gh pr merge --auto --rebase "$PR_URL" 26 | env: 27 | PR_URL: ${{github.event.pull_request.html_url}} 28 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 29 | 30 | - name: Auto-merge dependabot PRs for go-openapi patches 31 | if: contains(steps.metadata.outputs.dependency-group, 'go-openapi-dependencies') && (steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch') 32 | run: gh pr merge --auto --rebase "$PR_URL" 33 | env: 34 | PR_URL: ${{github.event.pull_request.html_url}} 35 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 36 | 37 | - name: Auto-merge dependabot PRs for golang.org updates 38 | if: contains(steps.metadata.outputs.dependency-group, 'golang.org-dependencies') 39 | run: gh pr merge --auto --rebase "$PR_URL" 40 | env: 41 | PR_URL: ${{github.event.pull_request.html_url}} 42 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: go test 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | 10 | pull_request: 11 | 12 | jobs: 13 | lint: 14 | name: Lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: stable 21 | check-latest: true 22 | cache: true 23 | - name: golangci-lint 24 | uses: golangci/golangci-lint-action@v8 25 | with: 26 | version: latest 27 | only-new-issues: true 28 | skip-cache: true 29 | 30 | test: 31 | name: Unit tests 32 | runs-on: ${{ matrix.os }} 33 | 34 | strategy: 35 | matrix: 36 | os: [ ubuntu-latest, macos-latest, windows-latest ] 37 | go_version: ['oldstable', 'stable', '1.20'] 38 | 39 | steps: 40 | - uses: actions/setup-go@v5 41 | with: 42 | go-version: '${{ matrix.go_version }}' 43 | check-latest: true 44 | cache: true 45 | 46 | - uses: actions/checkout@v4 47 | - name: Run unit tests 48 | shell: bash 49 | run: go test -v -race -coverprofile="coverage-${{ matrix.os }}.${{ matrix.go_version }}.out" -covermode=atomic -coverpkg=$(go list)/... ./... 50 | 51 | - name: Upload coverage to codecov 52 | uses: codecov/codecov-action@v5 53 | with: 54 | files: './coverage-${{ matrix.os }}.${{ matrix.go_version }}.out' 55 | flags: '${{ matrix.go_version }}-${{ matrix.os }}' 56 | fail_ci_if_error: false 57 | verbose: true 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | secrets.yml 2 | coverage.out 3 | *.cov 4 | *.out 5 | playground 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: all 4 | disable: 5 | - cyclop 6 | - depguard 7 | - err113 # disabled temporarily: there are just too many issues to address 8 | - errchkjson 9 | - errorlint 10 | - exhaustruct 11 | - forcetypeassert 12 | - funlen 13 | - gochecknoglobals 14 | - gochecknoinits 15 | - gocognit 16 | - godot 17 | - godox 18 | - gosmopolitan 19 | - inamedparam 20 | - ireturn 21 | - lll 22 | - musttag 23 | - nestif 24 | - nilerr # nilerr crashes on this repo 25 | - nlreturn 26 | - nonamedreturns 27 | - paralleltest 28 | - testpackage 29 | - thelper 30 | - tparallel 31 | - unparam 32 | - varnamelen 33 | - whitespace 34 | - wrapcheck 35 | - wsl 36 | settings: 37 | dupl: 38 | threshold: 200 39 | goconst: 40 | min-len: 2 41 | min-occurrences: 3 42 | gocyclo: 43 | min-complexity: 45 44 | exclusions: 45 | generated: lax 46 | presets: 47 | - comments 48 | - common-false-positives 49 | - legacy 50 | - std-error-handling 51 | paths: 52 | - third_party$ 53 | - builtin$ 54 | - examples$ 55 | formatters: 56 | enable: 57 | - gofmt 58 | - goimports 59 | exclusions: 60 | generated: lax 61 | paths: 62 | - third_party$ 63 | - builtin$ 64 | - examples$ 65 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ivan+abuse@flanders.co.nz. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # runtime [![Build Status](https://github.com/go-openapi/runtime/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/runtime/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/runtime/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/runtime) 2 | 3 | [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) 4 | [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/runtime/master/LICENSE) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/runtime.svg)](https://pkg.go.dev/github.com/go-openapi/runtime) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/runtime)](https://goreportcard.com/report/github.com/go-openapi/runtime) 7 | 8 | # go OpenAPI toolkit runtime 9 | 10 | The runtime component for use in code generation or as untyped usage. 11 | -------------------------------------------------------------------------------- /authinfo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/go-openapi/strfmt" 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestAuthInfoWriter(t *testing.T) { 26 | const bearerToken = "Bearer the-token-goes-here" 27 | 28 | hand := ClientAuthInfoWriterFunc(func(r ClientRequest, _ strfmt.Registry) error { 29 | return r.SetHeaderParam(HeaderAuthorization, bearerToken) 30 | }) 31 | 32 | tr := new(TestClientRequest) 33 | require.NoError(t, hand.AuthenticateRequest(tr, nil)) 34 | assert.Equal(t, bearerToken, tr.Headers.Get(HeaderAuthorization)) 35 | } 36 | -------------------------------------------------------------------------------- /client/auth_info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "encoding/base64" 19 | 20 | "github.com/go-openapi/strfmt" 21 | 22 | "github.com/go-openapi/runtime" 23 | ) 24 | 25 | // PassThroughAuth never manipulates the request 26 | var PassThroughAuth runtime.ClientAuthInfoWriter 27 | 28 | func init() { 29 | PassThroughAuth = runtime.ClientAuthInfoWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error { return nil }) 30 | } 31 | 32 | // BasicAuth provides a basic auth info writer 33 | func BasicAuth(username, password string) runtime.ClientAuthInfoWriter { 34 | return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { 35 | encoded := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) 36 | return r.SetHeaderParam(runtime.HeaderAuthorization, "Basic "+encoded) 37 | }) 38 | } 39 | 40 | // APIKeyAuth provides an API key auth info writer 41 | func APIKeyAuth(name, in, value string) runtime.ClientAuthInfoWriter { 42 | if in == "query" { 43 | return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { 44 | return r.SetQueryParam(name, value) 45 | }) 46 | } 47 | 48 | if in == "header" { 49 | return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { 50 | return r.SetHeaderParam(name, value) 51 | }) 52 | } 53 | return nil 54 | } 55 | 56 | // BearerToken provides a header based oauth2 bearer access token auth info writer 57 | func BearerToken(token string) runtime.ClientAuthInfoWriter { 58 | return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { 59 | return r.SetHeaderParam(runtime.HeaderAuthorization, "Bearer "+token) 60 | }) 61 | } 62 | 63 | // Compose combines multiple ClientAuthInfoWriters into a single one. 64 | // Useful when multiple auth headers are needed. 65 | func Compose(auths ...runtime.ClientAuthInfoWriter) runtime.ClientAuthInfoWriter { 66 | return runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error { 67 | for _, auth := range auths { 68 | if auth == nil { 69 | continue 70 | } 71 | if err := auth.AuthenticateRequest(r, nil); err != nil { 72 | return err 73 | } 74 | } 75 | return nil 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /client/auth_info_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "net/http" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | 24 | "github.com/go-openapi/runtime" 25 | ) 26 | 27 | func TestBasicAuth(t *testing.T) { 28 | r := newRequest(http.MethodGet, "/", nil) 29 | 30 | writer := BasicAuth("someone", "with a password") 31 | err := writer.AuthenticateRequest(r, nil) 32 | require.NoError(t, err) 33 | 34 | req := new(http.Request) 35 | req.Header = make(http.Header) 36 | req.Header.Set(runtime.HeaderAuthorization, r.header.Get(runtime.HeaderAuthorization)) 37 | usr, pw, ok := req.BasicAuth() 38 | require.True(t, ok) 39 | assert.Equal(t, "someone", usr) 40 | assert.Equal(t, "with a password", pw) 41 | } 42 | 43 | func TestAPIKeyAuth_Query(t *testing.T) { 44 | r := newRequest(http.MethodGet, "/", nil) 45 | 46 | writer := APIKeyAuth("api_key", "query", "the-shared-key") 47 | err := writer.AuthenticateRequest(r, nil) 48 | require.NoError(t, err) 49 | 50 | assert.Equal(t, "the-shared-key", r.query.Get("api_key")) 51 | } 52 | 53 | func TestAPIKeyAuth_Header(t *testing.T) { 54 | r := newRequest(http.MethodGet, "/", nil) 55 | 56 | writer := APIKeyAuth("X-Api-Token", "header", "the-shared-key") 57 | err := writer.AuthenticateRequest(r, nil) 58 | require.NoError(t, err) 59 | 60 | assert.Equal(t, "the-shared-key", r.header.Get("X-Api-Token")) 61 | } 62 | 63 | func TestBearerTokenAuth(t *testing.T) { 64 | r := newRequest(http.MethodGet, "/", nil) 65 | 66 | writer := BearerToken("the-shared-token") 67 | err := writer.AuthenticateRequest(r, nil) 68 | require.NoError(t, err) 69 | 70 | assert.Equal(t, "Bearer the-shared-token", r.header.Get(runtime.HeaderAuthorization)) 71 | } 72 | 73 | func TestCompose(t *testing.T) { 74 | r := newRequest(http.MethodGet, "/", nil) 75 | 76 | writer := Compose(APIKeyAuth("X-Api-Key", "header", "the-api-key"), APIKeyAuth("X-Secret-Key", "header", "the-secret-key")) 77 | err := writer.AuthenticateRequest(r, nil) 78 | require.NoError(t, err) 79 | 80 | assert.Equal(t, "the-api-key", r.header.Get("X-Api-Key")) 81 | assert.Equal(t, "the-secret-key", r.header.Get("X-Secret-Key")) 82 | } 83 | -------------------------------------------------------------------------------- /client/keepalive.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "sync/atomic" 7 | ) 8 | 9 | // KeepAliveTransport drains the remaining body from a response 10 | // so that go will reuse the TCP connections. 11 | // This is not enabled by default because there are servers where 12 | // the response never gets closed and that would make the code hang forever. 13 | // So instead it's provided as a http client middleware that can be used to override 14 | // any request. 15 | func KeepAliveTransport(rt http.RoundTripper) http.RoundTripper { 16 | return &keepAliveTransport{wrapped: rt} 17 | } 18 | 19 | type keepAliveTransport struct { 20 | wrapped http.RoundTripper 21 | } 22 | 23 | func (k *keepAliveTransport) RoundTrip(r *http.Request) (*http.Response, error) { 24 | resp, err := k.wrapped.RoundTrip(r) 25 | if err != nil { 26 | return resp, err 27 | } 28 | resp.Body = &drainingReadCloser{rdr: resp.Body} 29 | return resp, nil 30 | } 31 | 32 | type drainingReadCloser struct { 33 | rdr io.ReadCloser 34 | seenEOF uint32 35 | } 36 | 37 | func (d *drainingReadCloser) Read(p []byte) (n int, err error) { 38 | n, err = d.rdr.Read(p) 39 | if err == io.EOF || n == 0 { 40 | atomic.StoreUint32(&d.seenEOF, 1) 41 | } 42 | return 43 | } 44 | 45 | func (d *drainingReadCloser) Close() error { 46 | // drain buffer 47 | if atomic.LoadUint32(&d.seenEOF) != 1 { 48 | // If the reader side (a HTTP server) is misbehaving, it still may send 49 | // some bytes, but the closer ignores them to keep the underling 50 | // connection open. 51 | _, _ = io.Copy(io.Discard, d.rdr) 52 | } 53 | return d.rdr.Close() 54 | } 55 | -------------------------------------------------------------------------------- /client/keepalive_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func newCountingReader(rdr io.Reader, readOnce bool) *countingReadCloser { 13 | return &countingReadCloser{ 14 | rdr: rdr, 15 | readOnce: readOnce, 16 | } 17 | } 18 | 19 | type countingReadCloser struct { 20 | rdr io.Reader 21 | readOnce bool 22 | readCalled int 23 | closeCalled int 24 | } 25 | 26 | func (c *countingReadCloser) Read(b []byte) (int, error) { 27 | c.readCalled++ 28 | if c.readCalled > 1 && c.readOnce { 29 | return 0, io.EOF 30 | } 31 | return c.rdr.Read(b) 32 | } 33 | 34 | func (c *countingReadCloser) Close() error { 35 | c.closeCalled++ 36 | return nil 37 | } 38 | 39 | func TestDrainingReadCloser(t *testing.T) { 40 | rdr := newCountingReader(bytes.NewBufferString("There are many things to do"), false) 41 | prevDisc := io.Discard 42 | disc := bytes.NewBuffer(nil) 43 | io.Discard = disc 44 | defer func() { io.Discard = prevDisc }() 45 | 46 | buf := make([]byte, 5) 47 | ts := &drainingReadCloser{rdr: rdr} 48 | _, err := ts.Read(buf) 49 | require.NoError(t, err) 50 | require.NoError(t, ts.Close()) 51 | assert.Equal(t, "There", string(buf)) 52 | assert.Equal(t, " are many things to do", disc.String()) 53 | assert.Equal(t, 3, rdr.readCalled) 54 | assert.Equal(t, 1, rdr.closeCalled) 55 | } 56 | 57 | func TestDrainingReadCloser_SeenEOF(t *testing.T) { 58 | rdr := newCountingReader(bytes.NewBufferString("There are many things to do"), true) 59 | prevDisc := io.Discard 60 | disc := bytes.NewBuffer(nil) 61 | io.Discard = disc 62 | defer func() { io.Discard = prevDisc }() 63 | 64 | buf := make([]byte, 5) 65 | ts := &drainingReadCloser{rdr: rdr} 66 | _, err := ts.Read(buf) 67 | require.NoError(t, err) 68 | _, err = ts.Read(nil) 69 | require.ErrorIs(t, err, io.EOF) 70 | require.NoError(t, ts.Close()) 71 | assert.Equal(t, "There", string(buf)) 72 | assert.Empty(t, disc.String()) 73 | assert.Equal(t, 2, rdr.readCalled) 74 | assert.Equal(t, 1, rdr.closeCalled) 75 | } 76 | -------------------------------------------------------------------------------- /client/opentelemetry_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/go-openapi/runtime" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | "go.opentelemetry.io/otel" 12 | "go.opentelemetry.io/otel/attribute" 13 | "go.opentelemetry.io/otel/codes" 14 | "go.opentelemetry.io/otel/propagation" 15 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 16 | "go.opentelemetry.io/otel/sdk/trace/tracetest" 17 | "go.opentelemetry.io/otel/trace" 18 | ) 19 | 20 | func Test_OpenTelemetryRuntime_submit(t *testing.T) { 21 | t.Parallel() 22 | 23 | exporter := tracetest.NewInMemoryExporter() 24 | 25 | tp := tracesdk.NewTracerProvider( 26 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 27 | tracesdk.WithSyncer(exporter), 28 | ) 29 | 30 | otel.SetTracerProvider(tp) 31 | 32 | tracer := tp.Tracer("go-runtime") 33 | ctx, span := tracer.Start(context.Background(), "op") 34 | defer span.End() 35 | 36 | assertOpenTelemetrySubmit(t, testOperation(ctx), exporter, 1) 37 | } 38 | 39 | func Test_OpenTelemetryRuntime_submit_nilAuthInfo(t *testing.T) { 40 | t.Parallel() 41 | 42 | exporter := tracetest.NewInMemoryExporter() 43 | 44 | tp := tracesdk.NewTracerProvider( 45 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 46 | tracesdk.WithSyncer(exporter), 47 | ) 48 | 49 | otel.SetTracerProvider(tp) 50 | 51 | tracer := tp.Tracer("go-runtime") 52 | ctx, span := tracer.Start(context.Background(), "op") 53 | defer span.End() 54 | 55 | operation := testOperation(ctx) 56 | operation.AuthInfo = nil 57 | assertOpenTelemetrySubmit(t, operation, exporter, 1) 58 | } 59 | 60 | func Test_OpenTelemetryRuntime_submit_nilContext(t *testing.T) { 61 | exporter := tracetest.NewInMemoryExporter() 62 | 63 | tp := tracesdk.NewTracerProvider( 64 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 65 | tracesdk.WithSyncer(exporter), 66 | ) 67 | 68 | otel.SetTracerProvider(tp) 69 | 70 | tracer := tp.Tracer("go-runtime") 71 | ctx, span := tracer.Start(context.Background(), "op") 72 | defer span.End() 73 | operation := testOperation(ctx) 74 | operation.Context = nil 75 | 76 | assertOpenTelemetrySubmit(t, operation, exporter, 0) // just don't panic 77 | } 78 | 79 | func Test_injectOpenTelemetrySpanContext(t *testing.T) { 80 | t.Parallel() 81 | 82 | exporter := tracetest.NewInMemoryExporter() 83 | 84 | tp := tracesdk.NewTracerProvider( 85 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 86 | tracesdk.WithSyncer(exporter), 87 | ) 88 | 89 | otel.SetTracerProvider(tp) 90 | 91 | tracer := tp.Tracer("go-runtime") 92 | ctx, span := tracer.Start(context.Background(), "op") 93 | defer span.End() 94 | operation := testOperation(ctx) 95 | 96 | header := map[string][]string{} 97 | tr := newOpenTelemetryTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}}, "", nil) 98 | tr.config.Propagator = propagation.TraceContext{} 99 | _, err := tr.Submit(operation) 100 | require.NoError(t, err) 101 | 102 | assert.Len(t, header, 1) 103 | } 104 | 105 | func assertOpenTelemetrySubmit(t *testing.T, operation *runtime.ClientOperation, exporter *tracetest.InMemoryExporter, expectedSpanCount int) { 106 | header := map[string][]string{} 107 | tr := newOpenTelemetryTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}}, "remote_host", nil) 108 | 109 | _, err := tr.Submit(operation) 110 | require.NoError(t, err) 111 | 112 | spans := exporter.GetSpans() 113 | assert.Len(t, spans, expectedSpanCount) 114 | 115 | if expectedSpanCount != 1 { 116 | return 117 | } 118 | 119 | span := spans[0] 120 | assert.Equal(t, "getCluster", span.Name) 121 | assert.Equal(t, "go-openapi", span.InstrumentationLibrary.Name) 122 | assert.Equal(t, codes.Error, span.Status.Code) 123 | assert.Equal(t, []attribute.KeyValue{ 124 | attribute.String("net.peer.name", "remote_host"), 125 | attribute.String("http.route", "/kubernetes-clusters/{cluster_id}"), 126 | attribute.String("http.method", http.MethodGet), 127 | attribute.String("span.kind", trace.SpanKindClient.String()), 128 | attribute.String("http.scheme", schemeHTTPS), 129 | // NOTE: this becomes http.response.status_code with semconv v1.21 130 | attribute.Int("http.status_code", 490), 131 | }, span.Attributes) 132 | } 133 | -------------------------------------------------------------------------------- /client/opentracing.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/go-openapi/strfmt" 8 | "github.com/opentracing/opentracing-go" 9 | "github.com/opentracing/opentracing-go/ext" 10 | "github.com/opentracing/opentracing-go/log" 11 | 12 | "github.com/go-openapi/runtime" 13 | ) 14 | 15 | type tracingTransport struct { 16 | transport runtime.ClientTransport 17 | host string 18 | opts []opentracing.StartSpanOption 19 | } 20 | 21 | func newOpenTracingTransport(transport runtime.ClientTransport, host string, opts []opentracing.StartSpanOption, 22 | ) runtime.ClientTransport { 23 | return &tracingTransport{ 24 | transport: transport, 25 | host: host, 26 | opts: opts, 27 | } 28 | } 29 | 30 | func (t *tracingTransport) Submit(op *runtime.ClientOperation) (interface{}, error) { 31 | if op.Context == nil { 32 | return t.transport.Submit(op) 33 | } 34 | 35 | params := op.Params 36 | reader := op.Reader 37 | 38 | var span opentracing.Span 39 | defer func() { 40 | if span != nil { 41 | span.Finish() 42 | } 43 | }() 44 | 45 | op.Params = runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error { 46 | span = createClientSpan(op, req.GetHeaderParams(), t.host, t.opts) 47 | return params.WriteToRequest(req, reg) 48 | }) 49 | 50 | op.Reader = runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { 51 | if span != nil { 52 | code := response.Code() 53 | ext.HTTPStatusCode.Set(span, uint16(code)) //nolint:gosec // safe to convert regular HTTP codes, no adverse impact other than a garbled trace when converting a code larger than 65535 54 | if code >= http.StatusBadRequest { 55 | ext.Error.Set(span, true) 56 | } 57 | } 58 | return reader.ReadResponse(response, consumer) 59 | }) 60 | 61 | submit, err := t.transport.Submit(op) 62 | if err != nil && span != nil { 63 | ext.Error.Set(span, true) 64 | span.LogFields(log.Error(err)) 65 | } 66 | return submit, err 67 | } 68 | 69 | func createClientSpan(op *runtime.ClientOperation, header http.Header, host string, 70 | opts []opentracing.StartSpanOption) opentracing.Span { 71 | ctx := op.Context 72 | span := opentracing.SpanFromContext(ctx) 73 | 74 | if span != nil { 75 | opts = append(opts, ext.SpanKindRPCClient) 76 | span, _ = opentracing.StartSpanFromContextWithTracer( 77 | ctx, span.Tracer(), operationName(op), opts...) 78 | 79 | ext.Component.Set(span, "go-openapi") 80 | ext.PeerHostname.Set(span, host) 81 | span.SetTag("http.path", op.PathPattern) 82 | ext.HTTPMethod.Set(span, op.Method) 83 | 84 | _ = span.Tracer().Inject( 85 | span.Context(), 86 | opentracing.HTTPHeaders, 87 | opentracing.HTTPHeadersCarrier(header)) 88 | 89 | return span 90 | } 91 | return nil 92 | } 93 | 94 | func operationName(op *runtime.ClientOperation) string { 95 | if op.ID != "" { 96 | return op.ID 97 | } 98 | return fmt.Sprintf("%s_%s", op.Method, op.PathPattern) 99 | } 100 | -------------------------------------------------------------------------------- /client/opentracing_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "testing" 8 | 9 | "github.com/go-openapi/strfmt" 10 | "github.com/opentracing/opentracing-go" 11 | "github.com/opentracing/opentracing-go/ext" 12 | "github.com/opentracing/opentracing-go/mocktracer" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | 16 | "github.com/go-openapi/runtime" 17 | ) 18 | 19 | type tres struct { 20 | } 21 | 22 | func (r tres) Code() int { 23 | return 490 24 | } 25 | func (r tres) Message() string { 26 | return "the message" 27 | } 28 | func (r tres) GetHeader(_ string) string { 29 | return "the header" 30 | } 31 | func (r tres) GetHeaders(_ string) []string { 32 | return []string{"the headers", "the headers2"} 33 | } 34 | func (r tres) Body() io.ReadCloser { 35 | return io.NopCloser(bytes.NewBufferString("the content")) 36 | } 37 | 38 | type mockRuntime struct { 39 | req runtime.TestClientRequest 40 | } 41 | 42 | func (m *mockRuntime) Submit(operation *runtime.ClientOperation) (interface{}, error) { 43 | _ = operation.Params.WriteToRequest(&m.req, nil) 44 | _, _ = operation.Reader.ReadResponse(&tres{}, nil) 45 | return map[string]interface{}{}, nil 46 | } 47 | 48 | func testOperation(ctx context.Context) *runtime.ClientOperation { 49 | return &runtime.ClientOperation{ 50 | ID: "getCluster", 51 | Method: "GET", 52 | PathPattern: "/kubernetes-clusters/{cluster_id}", 53 | ProducesMediaTypes: []string{"application/json"}, 54 | ConsumesMediaTypes: []string{"application/json"}, 55 | Schemes: []string{schemeHTTPS}, 56 | Reader: runtime.ClientResponseReaderFunc(func(runtime.ClientResponse, runtime.Consumer) (interface{}, error) { 57 | return nil, nil 58 | }), 59 | Params: runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error { 60 | return nil 61 | }), 62 | AuthInfo: PassThroughAuth, 63 | Context: ctx, 64 | } 65 | } 66 | 67 | func Test_TracingRuntime_submit(t *testing.T) { 68 | t.Parallel() 69 | tracer := mocktracer.New() 70 | _, ctx := opentracing.StartSpanFromContextWithTracer(context.Background(), tracer, "op") 71 | testSubmit(t, testOperation(ctx), tracer, 1) 72 | } 73 | 74 | func Test_TracingRuntime_submit_nilAuthInfo(t *testing.T) { 75 | t.Parallel() 76 | tracer := mocktracer.New() 77 | _, ctx := opentracing.StartSpanFromContextWithTracer(context.Background(), tracer, "op") 78 | operation := testOperation(ctx) 79 | operation.AuthInfo = nil 80 | testSubmit(t, operation, tracer, 1) 81 | } 82 | 83 | func Test_TracingRuntime_submit_nilContext(t *testing.T) { 84 | t.Parallel() 85 | tracer := mocktracer.New() 86 | _, ctx := opentracing.StartSpanFromContextWithTracer(context.Background(), tracer, "op") 87 | operation := testOperation(ctx) 88 | operation.Context = nil 89 | testSubmit(t, operation, tracer, 0) // just don't panic 90 | } 91 | 92 | func testSubmit(t *testing.T, operation *runtime.ClientOperation, tracer *mocktracer.MockTracer, expectedSpans int) { 93 | 94 | header := map[string][]string{} 95 | r := newOpenTracingTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}}, 96 | "remote_host", 97 | []opentracing.StartSpanOption{opentracing.Tag{ 98 | Key: string(ext.PeerService), 99 | Value: "service", 100 | }}) 101 | 102 | _, err := r.Submit(operation) 103 | require.NoError(t, err) 104 | 105 | assert.Len(t, tracer.FinishedSpans(), expectedSpans) 106 | 107 | if expectedSpans == 1 { 108 | span := tracer.FinishedSpans()[0] 109 | assert.Equal(t, "getCluster", span.OperationName) 110 | assert.Equal(t, map[string]interface{}{ 111 | "component": "go-openapi", 112 | "http.method": "GET", 113 | "http.path": "/kubernetes-clusters/{cluster_id}", 114 | "http.status_code": uint16(490), 115 | "peer.hostname": "remote_host", 116 | "peer.service": "service", 117 | "span.kind": ext.SpanKindRPCClientEnum, 118 | "error": true, 119 | }, span.Tags()) 120 | } 121 | } 122 | 123 | func Test_injectSpanContext(t *testing.T) { 124 | t.Parallel() 125 | tracer := mocktracer.New() 126 | _, ctx := opentracing.StartSpanFromContextWithTracer(context.Background(), tracer, "op") 127 | header := map[string][]string{} 128 | createClientSpan(testOperation(ctx), header, "", nil) 129 | 130 | // values are random - just check that something was injected 131 | assert.Len(t, header, 3) 132 | } 133 | -------------------------------------------------------------------------------- /client/response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "io" 19 | "net/http" 20 | 21 | "github.com/go-openapi/runtime" 22 | ) 23 | 24 | var _ runtime.ClientResponse = response{} 25 | 26 | func newResponse(resp *http.Response) runtime.ClientResponse { return response{resp: resp} } 27 | 28 | type response struct { 29 | resp *http.Response 30 | } 31 | 32 | func (r response) Code() int { 33 | return r.resp.StatusCode 34 | } 35 | 36 | func (r response) Message() string { 37 | return r.resp.Status 38 | } 39 | 40 | func (r response) GetHeader(name string) string { 41 | return r.resp.Header.Get(name) 42 | } 43 | 44 | func (r response) GetHeaders(name string) []string { 45 | return r.resp.Header.Values(name) 46 | } 47 | 48 | func (r response) Body() io.ReadCloser { 49 | return r.resp.Body 50 | } 51 | -------------------------------------------------------------------------------- /client/response_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package client 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "net/http" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | "github.com/go-openapi/runtime" 26 | ) 27 | 28 | func TestResponse(t *testing.T) { 29 | under := new(http.Response) 30 | under.Status = "the status message" 31 | under.StatusCode = 392 32 | under.Header = make(http.Header) 33 | under.Header.Set("Blah", "blahblah") 34 | under.Body = io.NopCloser(bytes.NewBufferString("some content")) 35 | 36 | var resp runtime.ClientResponse = response{under} 37 | assert.EqualValues(t, under.StatusCode, resp.Code()) 38 | assert.Equal(t, under.Status, resp.Message()) 39 | assert.Equal(t, "blahblah", resp.GetHeader("blah")) 40 | assert.Equal(t, []string{"blahblah"}, resp.GetHeaders("blah")) 41 | assert.Equal(t, under.Body, resp.Body()) 42 | } 43 | -------------------------------------------------------------------------------- /client_auth_info.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import "github.com/go-openapi/strfmt" 18 | 19 | // A ClientAuthInfoWriterFunc converts a function to a request writer interface 20 | type ClientAuthInfoWriterFunc func(ClientRequest, strfmt.Registry) error 21 | 22 | // AuthenticateRequest adds authentication data to the request 23 | func (fn ClientAuthInfoWriterFunc) AuthenticateRequest(req ClientRequest, reg strfmt.Registry) error { 24 | return fn(req, reg) 25 | } 26 | 27 | // A ClientAuthInfoWriter implementor knows how to write authentication info to a request 28 | type ClientAuthInfoWriter interface { 29 | AuthenticateRequest(ClientRequest, strfmt.Registry) error 30 | } 31 | -------------------------------------------------------------------------------- /client_operation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | ) 21 | 22 | // ClientOperation represents the context for a swagger operation to be submitted to the transport 23 | type ClientOperation struct { 24 | ID string 25 | Method string 26 | PathPattern string 27 | ProducesMediaTypes []string 28 | ConsumesMediaTypes []string 29 | Schemes []string 30 | AuthInfo ClientAuthInfoWriter 31 | Params ClientRequestWriter 32 | Reader ClientResponseReader 33 | Context context.Context //nolint:containedctx // we precisely want this type to contain the request context 34 | Client *http.Client 35 | } 36 | 37 | // A ClientTransport implementor knows how to submit Request objects to some destination 38 | type ClientTransport interface { 39 | // Submit(string, RequestWriter, ResponseReader, AuthInfoWriter) (interface{}, error) 40 | Submit(*ClientOperation) (interface{}, error) 41 | } 42 | -------------------------------------------------------------------------------- /client_request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "io" 19 | "net/http" 20 | "net/url" 21 | "time" 22 | 23 | "github.com/go-openapi/strfmt" 24 | ) 25 | 26 | // ClientRequestWriterFunc converts a function to a request writer interface 27 | type ClientRequestWriterFunc func(ClientRequest, strfmt.Registry) error 28 | 29 | // WriteToRequest adds data to the request 30 | func (fn ClientRequestWriterFunc) WriteToRequest(req ClientRequest, reg strfmt.Registry) error { 31 | return fn(req, reg) 32 | } 33 | 34 | // ClientRequestWriter is an interface for things that know how to write to a request 35 | type ClientRequestWriter interface { 36 | WriteToRequest(ClientRequest, strfmt.Registry) error 37 | } 38 | 39 | // ClientRequest is an interface for things that know how to 40 | // add information to a swagger client request. 41 | type ClientRequest interface { //nolint:interfacebloat // a swagger-capable request is quite rich, hence the many getter/setters 42 | SetHeaderParam(string, ...string) error 43 | 44 | GetHeaderParams() http.Header 45 | 46 | SetQueryParam(string, ...string) error 47 | 48 | SetFormParam(string, ...string) error 49 | 50 | SetPathParam(string, string) error 51 | 52 | GetQueryParams() url.Values 53 | 54 | SetFileParam(string, ...NamedReadCloser) error 55 | 56 | SetBodyParam(interface{}) error 57 | 58 | SetTimeout(time.Duration) error 59 | 60 | GetMethod() string 61 | 62 | GetPath() string 63 | 64 | GetBody() []byte 65 | 66 | GetBodyParam() interface{} 67 | 68 | GetFileParam() map[string][]NamedReadCloser 69 | } 70 | 71 | // NamedReadCloser represents a named ReadCloser interface 72 | type NamedReadCloser interface { 73 | io.ReadCloser 74 | Name() string 75 | } 76 | 77 | // NamedReader creates a NamedReadCloser for use as file upload 78 | func NamedReader(name string, rdr io.Reader) NamedReadCloser { 79 | rc, ok := rdr.(io.ReadCloser) 80 | if !ok { 81 | rc = io.NopCloser(rdr) 82 | } 83 | return &namedReadCloser{ 84 | name: name, 85 | cr: rc, 86 | } 87 | } 88 | 89 | type namedReadCloser struct { 90 | name string 91 | cr io.ReadCloser 92 | } 93 | 94 | func (n *namedReadCloser) Close() error { 95 | return n.cr.Close() 96 | } 97 | func (n *namedReadCloser) Read(p []byte) (int, error) { 98 | return n.cr.Read(p) 99 | } 100 | func (n *namedReadCloser) Name() string { 101 | return n.name 102 | } 103 | 104 | type TestClientRequest struct { 105 | Headers http.Header 106 | Body interface{} 107 | } 108 | 109 | func (t *TestClientRequest) SetHeaderParam(name string, values ...string) error { 110 | if t.Headers == nil { 111 | t.Headers = make(http.Header) 112 | } 113 | t.Headers.Set(name, values[0]) 114 | return nil 115 | } 116 | 117 | func (t *TestClientRequest) SetQueryParam(_ string, _ ...string) error { return nil } 118 | 119 | func (t *TestClientRequest) SetFormParam(_ string, _ ...string) error { return nil } 120 | 121 | func (t *TestClientRequest) SetPathParam(_ string, _ string) error { return nil } 122 | 123 | func (t *TestClientRequest) SetFileParam(_ string, _ ...NamedReadCloser) error { return nil } 124 | 125 | func (t *TestClientRequest) SetBodyParam(body interface{}) error { 126 | t.Body = body 127 | return nil 128 | } 129 | 130 | func (t *TestClientRequest) SetTimeout(time.Duration) error { 131 | return nil 132 | } 133 | 134 | func (t *TestClientRequest) GetQueryParams() url.Values { return nil } 135 | 136 | func (t *TestClientRequest) GetMethod() string { return "" } 137 | 138 | func (t *TestClientRequest) GetPath() string { return "" } 139 | 140 | func (t *TestClientRequest) GetBody() []byte { return nil } 141 | 142 | func (t *TestClientRequest) GetBodyParam() interface{} { 143 | return t.Body 144 | } 145 | 146 | func (t *TestClientRequest) GetFileParam() map[string][]NamedReadCloser { 147 | return nil 148 | } 149 | 150 | func (t *TestClientRequest) GetHeaderParams() http.Header { 151 | return t.Headers 152 | } 153 | -------------------------------------------------------------------------------- /client_request_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/go-openapi/strfmt" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestRequestWriterFunc(t *testing.T) { 25 | hand := ClientRequestWriterFunc(func(r ClientRequest, _ strfmt.Registry) error { 26 | _ = r.SetHeaderParam("Blah", "blahblah") 27 | _ = r.SetBodyParam(struct{ Name string }{"Adriana"}) 28 | return nil 29 | }) 30 | 31 | tr := new(TestClientRequest) 32 | _ = hand.WriteToRequest(tr, nil) 33 | assert.Equal(t, "blahblah", tr.Headers.Get("Blah")) 34 | assert.Equal(t, "Adriana", tr.Body.(struct{ Name string }).Name) 35 | } 36 | -------------------------------------------------------------------------------- /client_response.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "io" 21 | ) 22 | 23 | // A ClientResponse represents a client response 24 | // This bridges between responses obtained from different transports 25 | type ClientResponse interface { 26 | Code() int 27 | Message() string 28 | GetHeader(string) string 29 | GetHeaders(string) []string 30 | Body() io.ReadCloser 31 | } 32 | 33 | // A ClientResponseReaderFunc turns a function into a ClientResponseReader interface implementation 34 | type ClientResponseReaderFunc func(ClientResponse, Consumer) (interface{}, error) 35 | 36 | // ReadResponse reads the response 37 | func (read ClientResponseReaderFunc) ReadResponse(resp ClientResponse, consumer Consumer) (interface{}, error) { 38 | return read(resp, consumer) 39 | } 40 | 41 | // A ClientResponseReader is an interface for things want to read a response. 42 | // An application of this is to create structs from response values 43 | type ClientResponseReader interface { 44 | ReadResponse(ClientResponse, Consumer) (interface{}, error) 45 | } 46 | 47 | // NewAPIError creates a new API error 48 | func NewAPIError(opName string, payload interface{}, code int) *APIError { 49 | return &APIError{ 50 | OperationName: opName, 51 | Response: payload, 52 | Code: code, 53 | } 54 | } 55 | 56 | // APIError wraps an error model and captures the status code 57 | type APIError struct { 58 | OperationName string 59 | Response interface{} 60 | Code int 61 | } 62 | 63 | func (o *APIError) Error() string { 64 | var resp []byte 65 | if err, ok := o.Response.(error); ok { 66 | resp = []byte("'" + err.Error() + "'") 67 | } else { 68 | resp, _ = json.Marshal(o.Response) 69 | } 70 | return fmt.Sprintf("%s (status %d): %s", o.OperationName, o.Code, resp) 71 | } 72 | 73 | func (o *APIError) String() string { 74 | return o.Error() 75 | } 76 | 77 | // IsSuccess returns true when this API response returns a 2xx status code 78 | func (o *APIError) IsSuccess() bool { 79 | const statusOK = 2 80 | return o.Code/100 == statusOK 81 | } 82 | 83 | // IsRedirect returns true when this API response returns a 3xx status code 84 | func (o *APIError) IsRedirect() bool { 85 | const statusRedirect = 3 86 | return o.Code/100 == statusRedirect 87 | } 88 | 89 | // IsClientError returns true when this API response returns a 4xx status code 90 | func (o *APIError) IsClientError() bool { 91 | const statusClientError = 4 92 | return o.Code/100 == statusClientError 93 | } 94 | 95 | // IsServerError returns true when this API response returns a 5xx status code 96 | func (o *APIError) IsServerError() bool { 97 | const statusServerError = 5 98 | return o.Code/100 == statusServerError 99 | } 100 | 101 | // IsCode returns true when this API response returns a given status code 102 | func (o *APIError) IsCode(code int) bool { 103 | return o.Code == code 104 | } 105 | 106 | // A ClientResponseStatus is a common interface implemented by all responses on the generated code 107 | // You can use this to treat any client response based on status code 108 | type ClientResponseStatus interface { 109 | IsSuccess() bool 110 | IsRedirect() bool 111 | IsClientError() bool 112 | IsServerError() bool 113 | IsCode(int) bool 114 | } 115 | -------------------------------------------------------------------------------- /client_response_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "io" 21 | "io/fs" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "github.com/stretchr/testify/require" 26 | ) 27 | 28 | type response struct { 29 | } 30 | 31 | func (r response) Code() int { 32 | return 490 33 | } 34 | func (r response) Message() string { 35 | return "the message" 36 | } 37 | func (r response) GetHeader(_ string) string { 38 | return "the header" 39 | } 40 | func (r response) GetHeaders(_ string) []string { 41 | return []string{"the headers", "the headers2"} 42 | } 43 | func (r response) Body() io.ReadCloser { 44 | return io.NopCloser(bytes.NewBufferString("the content")) 45 | } 46 | 47 | func TestResponseReaderFunc(t *testing.T) { 48 | var actual struct { 49 | Header, Message, Body string 50 | Code int 51 | } 52 | reader := ClientResponseReaderFunc(func(r ClientResponse, _ Consumer) (interface{}, error) { 53 | b, _ := io.ReadAll(r.Body()) 54 | actual.Body = string(b) 55 | actual.Code = r.Code() 56 | actual.Message = r.Message() 57 | actual.Header = r.GetHeader("blah") 58 | return actual, nil 59 | }) 60 | _, _ = reader.ReadResponse(response{}, nil) 61 | assert.Equal(t, "the content", actual.Body) 62 | assert.Equal(t, "the message", actual.Message) 63 | assert.Equal(t, "the header", actual.Header) 64 | assert.Equal(t, 490, actual.Code) 65 | } 66 | 67 | func TestResponseReaderFuncError(t *testing.T) { 68 | reader := ClientResponseReaderFunc(func(r ClientResponse, _ Consumer) (interface{}, error) { 69 | _, _ = io.ReadAll(r.Body()) 70 | return nil, NewAPIError("fake", errors.New("writer closed"), 490) 71 | }) 72 | _, err := reader.ReadResponse(response{}, nil) 73 | require.Error(t, err) 74 | require.ErrorContains(t, err, "writer closed") 75 | 76 | reader = func(r ClientResponse, _ Consumer) (interface{}, error) { 77 | _, _ = io.ReadAll(r.Body()) 78 | err := &fs.PathError{ 79 | Op: "write", 80 | Path: "path/to/fake", 81 | Err: fs.ErrClosed, 82 | } 83 | return nil, NewAPIError("fake", err, 200) 84 | } 85 | _, err = reader.ReadResponse(response{}, nil) 86 | require.Error(t, err) 87 | assert.Contains(t, err.Error(), "file already closed") 88 | 89 | } 90 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | const ( 18 | // HeaderContentType represents a http content-type header, it's value is supposed to be a mime type 19 | HeaderContentType = "Content-Type" 20 | 21 | // HeaderTransferEncoding represents a http transfer-encoding header. 22 | HeaderTransferEncoding = "Transfer-Encoding" 23 | 24 | // HeaderAccept the Accept header 25 | HeaderAccept = "Accept" 26 | // HeaderAuthorization the Authorization header 27 | HeaderAuthorization = "Authorization" 28 | 29 | charsetKey = "charset" 30 | 31 | // DefaultMime the default fallback mime type 32 | DefaultMime = "application/octet-stream" 33 | // JSONMime the json mime type 34 | JSONMime = "application/json" 35 | // YAMLMime the yaml mime type 36 | YAMLMime = "application/x-yaml" 37 | // XMLMime the xml mime type 38 | XMLMime = "application/xml" 39 | // TextMime the text mime type 40 | TextMime = "text/plain" 41 | // HTMLMime the html mime type 42 | HTMLMime = "text/html" 43 | // CSVMime the csv mime type 44 | CSVMime = "text/csv" 45 | // MultipartFormMime the multipart form mime type 46 | MultipartFormMime = "multipart/form-data" 47 | // URLencodedFormMime the url encoded form mime type 48 | URLencodedFormMime = "application/x-www-form-urlencoded" 49 | ) 50 | -------------------------------------------------------------------------------- /csv_options.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | ) 7 | 8 | // CSVOpts alter the behavior of the CSV consumer or producer. 9 | type CSVOpt func(*csvOpts) 10 | 11 | type csvOpts struct { 12 | csvReader csv.Reader 13 | csvWriter csv.Writer 14 | skippedLines int 15 | closeStream bool 16 | } 17 | 18 | // WithCSVReaderOpts specifies the options to csv.Reader 19 | // when reading CSV. 20 | func WithCSVReaderOpts(reader csv.Reader) CSVOpt { 21 | return func(o *csvOpts) { 22 | o.csvReader = reader 23 | } 24 | } 25 | 26 | // WithCSVWriterOpts specifies the options to csv.Writer 27 | // when writing CSV. 28 | func WithCSVWriterOpts(writer csv.Writer) CSVOpt { 29 | return func(o *csvOpts) { 30 | o.csvWriter = writer 31 | } 32 | } 33 | 34 | // WithCSVSkipLines will skip header lines. 35 | func WithCSVSkipLines(skipped int) CSVOpt { 36 | return func(o *csvOpts) { 37 | o.skippedLines = skipped 38 | } 39 | } 40 | 41 | func WithCSVClosesStream() CSVOpt { 42 | return func(o *csvOpts) { 43 | o.closeStream = true 44 | } 45 | } 46 | 47 | func (o csvOpts) applyToReader(in *csv.Reader) { 48 | if o.csvReader.Comma != 0 { 49 | in.Comma = o.csvReader.Comma 50 | } 51 | if o.csvReader.Comment != 0 { 52 | in.Comment = o.csvReader.Comment 53 | } 54 | if o.csvReader.FieldsPerRecord != 0 { 55 | in.FieldsPerRecord = o.csvReader.FieldsPerRecord 56 | } 57 | 58 | in.LazyQuotes = o.csvReader.LazyQuotes 59 | in.TrimLeadingSpace = o.csvReader.TrimLeadingSpace 60 | in.ReuseRecord = o.csvReader.ReuseRecord 61 | } 62 | 63 | func (o csvOpts) applyToWriter(in *csv.Writer) { 64 | if o.csvWriter.Comma != 0 { 65 | in.Comma = o.csvWriter.Comma 66 | } 67 | in.UseCRLF = o.csvWriter.UseCRLF 68 | } 69 | 70 | func csvOptsWithDefaults(opts []CSVOpt) csvOpts { 71 | var o csvOpts 72 | for _, apply := range opts { 73 | apply(&o) 74 | } 75 | 76 | return o 77 | } 78 | 79 | type CSVWriter interface { 80 | Write([]string) error 81 | Flush() 82 | Error() error 83 | } 84 | 85 | type CSVReader interface { 86 | Read() ([]string, error) 87 | } 88 | 89 | var ( 90 | _ CSVWriter = &csvRecordsWriter{} 91 | _ CSVReader = &csvRecordsWriter{} 92 | ) 93 | 94 | // csvRecordsWriter is an internal container to move CSV records back and forth 95 | type csvRecordsWriter struct { 96 | i int 97 | records [][]string 98 | } 99 | 100 | func (w *csvRecordsWriter) Write(record []string) error { 101 | w.records = append(w.records, record) 102 | 103 | return nil 104 | } 105 | 106 | func (w *csvRecordsWriter) Read() ([]string, error) { 107 | if w.i >= len(w.records) { 108 | return nil, io.EOF 109 | } 110 | defer func() { 111 | w.i++ 112 | }() 113 | 114 | return w.records[w.i], nil 115 | } 116 | 117 | func (w *csvRecordsWriter) Flush() {} 118 | 119 | func (w *csvRecordsWriter) Error() error { 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /discard.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "io" 4 | 5 | // DiscardConsumer does absolutely nothing, it's a black hole. 6 | var DiscardConsumer = ConsumerFunc(func(_ io.Reader, _ interface{}) error { return nil }) 7 | 8 | // DiscardProducer does absolutely nothing, it's a black hole. 9 | var DiscardProducer = ProducerFunc(func(_ io.Writer, _ interface{}) error { return nil }) 10 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import "github.com/go-openapi/swag" 18 | 19 | type File = swag.File 20 | -------------------------------------------------------------------------------- /file_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-openapi/spec" 7 | "github.com/go-openapi/validate" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestValidateFile(t *testing.T) { 13 | fileParam := spec.FileParam("f") 14 | validator := validate.NewParamValidator(fileParam, nil) 15 | 16 | result := validator.Validate("str") 17 | require.Len(t, result.Errors, 1) 18 | assert.Equal( 19 | t, 20 | `f in formData must be of type file: "string"`, 21 | result.Errors[0].Error(), 22 | ) 23 | 24 | result = validator.Validate(&File{}) 25 | assert.True(t, result.IsValid()) 26 | 27 | result = validator.Validate(File{}) 28 | assert.True(t, result.IsValid()) 29 | } 30 | -------------------------------------------------------------------------------- /fixtures/bugs/172/swagger.yml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: 1.0.0 4 | title: 'Test' 5 | schemes: 6 | - http 7 | produces: 8 | - application/vnd.cia.v1+json 9 | 10 | paths: 11 | /pets: 12 | get: 13 | description: Returns all pets from the system that the user has access to 14 | operationId: findPets 15 | parameters: 16 | - name: tags 17 | in: query 18 | description: tags to filter by 19 | required: false 20 | type: array 21 | items: 22 | type: string 23 | collectionFormat: csv 24 | - name: limit 25 | in: query 26 | description: maximum number of results to return 27 | required: false 28 | type: integer 29 | format: int32 30 | responses: 31 | '200': 32 | description: pet response 33 | schema: 34 | type: array 35 | items: 36 | $ref: '#/definitions/pet' 37 | default: 38 | description: unexpected error 39 | schema: 40 | $ref: '#/definitions/errorModel' 41 | 42 | post: 43 | description: Creates a new pet in the store. Duplicates are allowed 44 | operationId: addPet 45 | consumes: 46 | - application/vnd.cia.v1+json 47 | parameters: 48 | - name: pet 49 | in: body 50 | description: Pet to add to the store 51 | required: true 52 | schema: 53 | $ref: '#/definitions/newPet' 54 | responses: 55 | '200': 56 | description: pet response 57 | schema: 58 | $ref: '#/definitions/pet' 59 | default: 60 | description: unexpected error 61 | schema: 62 | $ref: '#/definitions/errorModel' 63 | 64 | definitions: 65 | pet: 66 | required: 67 | - id 68 | - name 69 | properties: 70 | id: 71 | type: integer 72 | format: int64 73 | name: 74 | type: string 75 | tag: 76 | type: string 77 | 78 | newPet: 79 | allOf: 80 | - $ref: '#/definitions/pet' 81 | - required: 82 | - name 83 | properties: 84 | id: 85 | type: integer 86 | format: int64 87 | name: 88 | type: string 89 | 90 | errorModel: 91 | required: 92 | - code 93 | - message 94 | properties: 95 | code: 96 | type: integer 97 | format: int32 98 | message: 99 | type: string 100 | -------------------------------------------------------------------------------- /fixtures/bugs/174/swagger.yml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: 1.0.0 4 | title: 'Test' 5 | schemes: 6 | - http 7 | 8 | paths: 9 | /pets: 10 | get: 11 | description: Returns all pets from the system that the user has access to 12 | operationId: findPets 13 | parameters: 14 | - name: tags 15 | in: query 16 | description: tags to filter by 17 | required: false 18 | type: array 19 | items: 20 | type: string 21 | collectionFormat: csv 22 | - name: limit 23 | in: query 24 | description: maximum number of results to return 25 | required: false 26 | type: integer 27 | format: int32 28 | responses: 29 | '200': 30 | description: pet response 31 | schema: 32 | type: array 33 | items: 34 | $ref: '#/definitions/pet' 35 | default: 36 | description: unexpected error 37 | schema: 38 | $ref: '#/definitions/errorModel' 39 | 40 | post: 41 | description: Creates a new pet in the store. Duplicates are allowed 42 | operationId: addPet 43 | consumes: 44 | - application/json 45 | produces: 46 | - application/json 47 | parameters: 48 | - name: pet 49 | in: body 50 | description: Pet to add to the store 51 | required: true 52 | schema: 53 | $ref: '#/definitions/newPet' 54 | responses: 55 | '200': 56 | description: pet response 57 | schema: 58 | $ref: '#/definitions/pet' 59 | default: 60 | description: unexpected error 61 | schema: 62 | $ref: '#/definitions/errorModel' 63 | 64 | definitions: 65 | pet: 66 | required: 67 | - id 68 | - name 69 | properties: 70 | id: 71 | type: integer 72 | format: int64 73 | name: 74 | type: string 75 | tag: 76 | type: string 77 | 78 | newPet: 79 | allOf: 80 | - $ref: '#/definitions/pet' 81 | - required: 82 | - name 83 | properties: 84 | id: 85 | type: integer 86 | format: int64 87 | name: 88 | type: string 89 | 90 | errorModel: 91 | required: 92 | - code 93 | - message 94 | properties: 95 | code: 96 | type: integer 97 | format: int32 98 | message: 99 | type: string 100 | -------------------------------------------------------------------------------- /fixtures/bugs/264/swagger.yml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | version: 1.0.0 4 | title: 'Test' 5 | schemes: 6 | - http 7 | produces: 8 | - application/json 9 | consumes: 10 | - application/json 11 | paths: 12 | /key/{id}: 13 | delete: 14 | parameters: 15 | - name: id 16 | in: path 17 | type: integer 18 | required: true 19 | responses: 20 | '200': 21 | description: OK 22 | -------------------------------------------------------------------------------- /fixtures/certs/myCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE/TCCAuWgAwIBAgIJAJ0kpLFo4pEzMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV 3 | BAMMCkdvIFN3YWdnZXIwHhcNMTYxMjIxMDgzMzM4WhcNMTgxMjIxMDgzMzM4WjAV 4 | MRMwEQYDVQQDDApHbyBTd2FnZ2VyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC 5 | CgKCAgEAzQ5NC1JBNNP79HPiUBAO59LoUMGbmSU9K9v+cQMuyyOuv0nwuiXc5anU 6 | J1BINqgLR1VJjwTnQsXSlsr2SPs/144KgTsgk/QpMXdlFQwfqLJBIFlsQQBbMx6L 7 | /2Ho6KE7z/qz6cqgKvYrGDu6ELUu016MbUsPWfhPBJE7Ftoajk5AIomDPmiTi0cZ 8 | wdhC8SB0aVVQ2IWrsusfgPeOQ+ZLa/WHmpJ2Syfq41i/VKllEeCrMwtMP2By2kA/ 9 | ufBLCnhr7yZ0u22O1Bl1+0XedWli2GiXyt1h9nQ5blTTKZi5grOzAgCcshb/bw1H 10 | 1hdJKMqkzbqt2Mxc/78PJbDgicJU1ap+fhfBmUviWIMML6eum2ObuKd4ihhXKfqp 11 | T/nSUA0P9565W71SLAHFLdZX/VSMZnoehkwIicVGgEzjlYj2j9qBc0CjYzbEtQXH 12 | TRGhbjMX5LSByeE6hwLM6hIbQL4nriRobar63rbOc74Tm1ed02R6BvQjgXgOGqAN 13 | BgCKKjfUIm0Qm2qV4WkwGIAOi+hdUpbNJ0X2dU/B00qLhar+h4NT9TW4PmKf4agk 14 | NZ6O3C1saGxjtuPnIdDxWTdRhPSUyjsllmWhrmkY2bsRB8Z47zqrdfyajXlPOmBM 15 | 1f0am4Zeo3ditBTfFqtA2LLQbn1yZwYJQ8+sESu6bsm3S89DFT0CAwEAAaNQME4w 16 | HQYDVR0OBBYEFN4BShcjqDbbgaGvPiGMNrUEi/RZMB8GA1UdIwQYMBaAFN4BShcj 17 | qDbbgaGvPiGMNrUEi/RZMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB 18 | AIqZYn+PFMcOwraYtXtAzJwU4gElWDjIA+xVMMzulHde8t7EWo+I5iDdtn+sHzhG 19 | B1oYFhKv/HIX9GR3XDYWluoUVA4Ed9eBamL0Qqzq4Era6ZN1VQd0egvyOT40UQ7m 20 | 2aNw1nLgWadCmsxeVMKQRdzct9G3dOfJp7K5WybINWTibNMTuoSuU5RwtzriK000 21 | C9pnxCD8boSNY/flOX0M5Mt3kv2JaIH2UsMKNGBf5+rXcKfhTE6JgiXorUEEztHP 22 | PFpZ6VFKDlr8QC/4aLYhOJ9LIloaxZyk/rccCuHbdPPX5XGA3Z9i/lxSoqtShYlS 23 | mt5vmdRwQob/ul6hPch3YRqD4VgeM1O80FEsWBK2WmGGH3wKNKea7u6dZyfQv3t3 24 | fUVmByAVMllVRA1YiKmBZ/kOeAMku5hpR9kzErCXZd/xrKWVym000RsvRb6apltM 25 | sYnlCyKfIdKxUXavO0Bf4+YoaN4/p3mZchxpLBwrzhPyUpGQ9b3TuGjoEmtG57yn 26 | 6I3U40/TouJR0aF7i1bAF5QJWYOS7OycJbHAIZiQx9ENDP3ZMfYNWQO6STFJAjvC 27 | C0u23DyiJWZqE4Uw51O7jWKh7bSEKWutwa0XKWrpxhUjHFX4qGigIvXpO9LMjR60 28 | YDhdCEmUiu/Hc0tt0QzyTA6w47TP0gXREeBLabzuEDPi 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /fixtures/certs/myCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /fixtures/certs/mycert1.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEqzCCApMCCQChJZEdSdrQkjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApH 3 | byBTd2FnZ2VyMB4XDTE2MTIyMTA4MzMzOFoXDTE3MTIyMTA4MzMzOFowGjEYMBYG 4 | A1UEAwwPZ29zd2FnZ2VyLmxvY2FsMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC 5 | CgKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOilpEUhm92KGRSMQXZEk+2TUgc 6 | dGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoYnkL3neoiXBdBVsgHkEPdP5ly 7 | uJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeALbrR027dMbY2XMC97FteeVaw1 8 | mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M4X5BPCWFu1oQhgVMEhodBoBj 9 | pHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4DWX4ma0uhu+zJew2XjCJkNfX 10 | wVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC45JxCAVi3tQCYGsg2xkX9yPj 11 | aXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xykqn6YOHyxIVoqd+9wo9Z1weH 12 | xCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2lxNXMQA0f3S8sWYe4f8QVazy 13 | ALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlXXBxHjzfpXraNGoSD4v6LxRxP 14 | dWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN6mHERA9wF2RQQzPddZ0MYmUF 15 | DI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0ifawyPi2V8f8CAwEAATANBgkq 16 | hkiG9w0BAQsFAAOCAgEAme1gyNQry3E5bj4XfdL4aNvZamzLaQVRlNZSHUzDhhpH 17 | 6N/DK/CAw4g4Msty7g3KBZPmldJhxH0bnSoRGMjFdKn9tVQeJOjaHQ2Z3cQWwdte 18 | iXtu2F38SVfP5HCh9ASQ9vQXahGOruUPUUNUnDLfOBea7vrT3DmVugXlMSmaYuSJ 19 | JdrbPzD48yy60AEDlCVpY2m1cEc5SmTkXbrAg2jhQd6ytaPQ28vGQnpZHSS/xWjC 20 | Hh68o5SUoGoFErZxPd0o2brHavi4YybYt7CXlWG2TJ89s3BCSPIHclNF2HjxRq/r 21 | 2Q/Ttzo3cRBxi3RBnrLdn4qNgJjZnWaLobjaWcs1fbI32allogLsiurCwZb0ToC0 22 | fNMzyHVNWY8BqsuyWyF2H0F9rklmqGFJSmrqt8kDLx0xpkZchGPIDSRh+f+PPDmE 23 | jGPPH2qxz4un0foJx99dtw18TPaplFo2LxRK89koTiQNyzAHwSn6PHGlyXhNPsUt 24 | K5GzjAu6B4uyldcg2m+4O/dbNdeqSczYAFenfEO7PRAy3AP7Lxs2xqQaNiA10965 25 | vYmCNIOuV24CuFEIrjOQkZeFCw+odsgFs5Nv8JfDdA+BRr+Haq8FVX8afEc0BEnr 26 | xY6f2fvgYTMvx0Z3UVT/XJ3POWHRL0HFLj5avHE0eOOkrcPbX6UsANd1v0F2BH8= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /fixtures/certs/mycert1.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /fixtures/certs/mycert1.req: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIEXzCCAkcCAQAwGjEYMBYGA1UEAwwPZ29zd2FnZ2VyLmxvY2FsMIICIjANBgkq 3 | hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs1MHhleossLBkYKYwOT+82RT651CfCOi 4 | lpEUhm92KGRSMQXZEk+2TUgcdGPeQNDNmbpXGzdk1HZkqWR5XKfSjWWxfmBlbBoY 5 | nkL3neoiXBdBVsgHkEPdP5lyuJRkohy6az1vnq2vLaI+YujStutf8hSdcPu9VeAL 6 | brR027dMbY2XMC97FteeVaw1mXmW9UHDVSV9UPBPswUOQWhjIADBk5IYaYASCY3M 7 | 4X5BPCWFu1oQhgVMEhodBoBjpHhHrfoDm1TwtT+dp53TmR10zpUiN+FcaVMsjqN4 8 | DWX4ma0uhu+zJew2XjCJkNfXwVqGFpe2Hx+lupOGs/kwBvQ4PYn5ydgcm5DTggBC 9 | 45JxCAVi3tQCYGsg2xkX9yPjaXYc8E4/aeQI9UZxUeR2siBn8ECX4gJTmPJbQ4Xy 10 | kqn6YOHyxIVoqd+9wo9Z1weHxCWtPGESg7l7Jn/6WQ5V8z6RzrquGi67asrpYpv2 11 | lxNXMQA0f3S8sWYe4f8QVazyALtu8+0XE17UPjlbNBqEfCIrMsYmL5VyMVbL0dlX 12 | XBxHjzfpXraNGoSD4v6LxRxPdWQgrhEZ6DmfiWfX8uhLdMwlvUxNXj33UDtM8dtN 13 | 6mHERA9wF2RQQzPddZ0MYmUFDI92i9mRC7Yzx6mcv/yUnFw213Jnzg297lW0Xp0i 14 | fawyPi2V8f8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQB7U21HoBp5Qfrwd8aA 15 | KzBY2BfEp5ouyn32kpfkB3Ha6+XJ69Lt1WHMSKnmYamlwZCSOS2uQ6DzdTLDfZpC 16 | 8PH5Gs32O9zJwUeSuYcUQGfcAenauu9gwC5ZnIbhOs5YTnEFquVsBqrNUKS+hLKJ 17 | sAPtucoqlLX5qSkv/BOK2X4os90LAmx+yB/yarAzZOO0ku8qXt+MHI+rOMPLTmm9 18 | kYhtyXejQaXLOVbvQ9b2gxHvMcyLhklc4KpJPRfPzOdNebHsf5o4Em6lxeglGw/A 19 | z05sBSAla69sEygcItZryQ4WjMRUpsLePXJrlSL5DYWGK6BX1gCkWtpXLqE1HgR3 20 | 4L/xvaJQ5ZWpLoyJoJauU37Zhd5dLNGpNiSSEA0BKOjj9Kjm8nvsJE9DgziTaG57 21 | qFLRkMkDdBdb5wOfVYI/MY9zc+igrFPQJkQ0Xkdza8yXegBldv1JRe+49zifysea 22 | Y/B+qWx8IpeHke0iEMqR6iWrw6oGBG/obHJ/V09DwC6iU8vot+pLr/bSyoUCUP30 23 | OEATJf50ic9oZYXgdT9oNBcAlAriuzoQuGi9nAKZJss6YkhooWoqXlXNQgAEc2gl 24 | WF4fNumXwVaPVeW2q36Xk1btHz7k+IeVUg1jaPMPUJ+1dgIOZA7FcoYotvF6StyX 25 | xoHybhvC7lbeif8EK7tJ2p4hug== 26 | -----END CERTIFICATE REQUEST----- 27 | -------------------------------------------------------------------------------- /fixtures/certs/myclient-ecc.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC3TCBxgIJAKElkR1J2tCUMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNVBAMMCkdv 3 | IFN3YWdnZXIwHhcNMTgwMTIyMTUyODA3WhcNMTkwMTIyMTUyODA3WjAYMRYwFAYD 4 | VQQDDA1UZXN0IEVDQyBDZXJ0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENlON 5 | 9ojzKxcMlsAIFcsc+JoMSOd1bREjEHAPaj77L0NjO6p605jT2TTLbj1lpDGD9XRc 6 | vw5iiHvhF41Sl454wjANBgkqhkiG9w0BAQsFAAOCAgEAyYwVGVG/Jzei9zxmWT+k 7 | LeK7OpGzOIyfBdEvQDFSfmyHoVJsR2LdicMJAI0gnVl5V0LdfBpu1MRz6Jdsto+u 8 | RKL1WdygESzWQ/LtqvT4rb5vLhkpCBY6Ua+SoTpY/93iOZpf1ArKOtHzL4Xop2Lb 9 | 6/0hHYU73vaBjd9YnA3T0jVFsI/otpfJhSY8FGdKSYKMf6rob9+iv2Tyjm1svkV0 10 | IBL0D0v/LlGeM8UqXC3ZLaHsTxWi2v6PNfRyFnSNoRX4+I9ejjYvjIKQ9giVcPFQ 11 | SfhR5xm0C0xxYVqoIb6gX6owlmX2duIaV6qjU5YSzwEZqkv0Ze9i+zztBVqBRA7q 12 | fC/AMSxtqo4+Faj+hxX9T4D15hysx76uS7LxCi8GkypSZTGkjhHdMRKa2jIEvW3A 13 | 9nKW4nnC5sEBDrOTwaH4Mn6zFik3r9LTfh1gljLu9Ieqizb1gXloFhWJYvC2UwXO 14 | ins3tX2VYBF7p6yIXRmc5nZlpFErGqu2MR/lwJKD6zGIJOzCza/4DP+Mppw+DSPN 15 | XkNJG05uymsaEZceupeBH0uCgVSuVaZ3nfA73RM+0evxsscii/Kw/VFNvNDy5fLg 16 | OQWRm6RlBTK2dRqpsfo9irjdd6NVC0EfqZceYIte/eWn9aPU5uTy/TzRG24ouKtY 17 | Ixs1usnXCabNN/n0AMI+xVc= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /fixtures/certs/myclient-ecc.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIHSMHoCAQAwGDEWMBQGA1UEAwwNVGVzdCBFQ0MgQ2VydDBZMBMGByqGSM49AgEG 3 | CCqGSM49AwEHA0IABDZTjfaI8ysXDJbACBXLHPiaDEjndW0RIxBwD2o++y9DYzuq 4 | etOY09k0y249ZaQxg/V0XL8OYoh74ReNUpeOeMKgADAKBggqhkjOPQQDAgNIADBF 5 | AiEAsqdXJEIuedKkuiavgfc0YXssFWBORAC37F5w+Z0kGEMCIDRGiCaZG4Z/Gutm 6 | id7N5T0Uxah0p5i6OzvCpYPN8f3Y 7 | -----END CERTIFICATE REQUEST----- 8 | -------------------------------------------------------------------------------- /fixtures/certs/myclient-ecc.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BggqhkjOPQMBBw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MHcCAQEEIJyZvFqw3os7TVGOSvK8XM1qysN32jG6G0AQ2mDxcRaaoAoGCCqGSM49 6 | AwEHoUQDQgAENlON9ojzKxcMlsAIFcsc+JoMSOd1bREjEHAPaj77L0NjO6p605jT 7 | 2TTLbj1lpDGD9XRcvw5iiHvhF41Sl454wg== 8 | -----END EC PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /fixtures/certs/myclient.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE7jCCAtYCCQChJZEdSdrQkzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDDApH 3 | byBTd2FnZ2VyMB4XDTE2MTIyMTA4MzM0N1oXDTE3MTIyMTA4MzM0N1owXTELMAkG 4 | A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAcMCE1pbGxicmFl 5 | MRIwEAYDVQQKDAlMb2NhbCBEZXYxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJ 6 | KoZIhvcNAQEBBQADggIPADCCAgoCggIBALGjhpJfej7btWCO4OCRJBliUAUyPMO8 7 | B649Qjn1Yiw9E1L5viByYJSihsfUQ7u2gHip7QdigKCA/s4+w8V2L0Dv8lCowCLk 8 | exf10b7XGQaOqhk2mlr/jOapAg0pKDoUlVErBBZK2s6UbD/gLXAbxudwxCFKJ1Y7 9 | d/Dw5aTl1vlWZpHzf2o9/ZCeHXf8Xu3aMIEPJ79wG0vzNZK7bL1r1lQVzACdHAr3 10 | 4HAQAvgWB4ZjKqN8z0vGC0N0MpaAuHD8fH8wQ5YiWBbDhDPFVzRYU8PcQjeZSMFq 11 | Oulew9KVm+vXtcMvteEoXMXwWlqAGlvnv7sskc/VbrLJJQaoswyKgy1QCKxVO47E 12 | f2iU4kP75iDYx6NpApdnpN3zxHMHyZDxuwmtoKealenxl5cZeHc6uUU1wXk+nmy7 13 | TrgW509mcopHzHj+Q0zyGUg/dRws3qXPAGZehJPoaYF1F54eiindF1yLMMH5osvy 14 | 1bNp2EQezOlY3P4gqW9VHq3CQvytmDbXqS0vPzVAsFu8YazM3Bs0mW2bBXrEsajW 15 | DSjrvbhdZjlL9j2jqwZ2nzyan88M5t5T0vZhcu+wKisATI1yLdV3oWvLmdFz/XA9 16 | L6UyosTiwC1MWPmkOY4mcHn/Px70f40+wO815pZ6FbjecxRSyMfAm6vDPWtLAMUr 17 | 1UoD4vasyvQNAgMBAAEwDQYJKoZIhvcNAQELBQADggIBACI85R1LfDwKpmWFdCeN 18 | M8O6TwSftxh113QzMvobYDMmBI+yVn0ZvpcfP7E+LWRjzNcDxMFbntbbFWt99Rcd 19 | rJek6CVxLJug53mUEFmvktreHsJ1T7cMbk1ZVroAurE7hZOWYM2HwXlKzVyXX0qh 20 | wR26HSuzQcGBfo5/8e6dzMuy0fUVkgDMu4oKt0+mGgS4kXsOyexfRRBkY9GPusVk 21 | gSzu/WbSGNxNvp/ewWNi8waqrN3noM83iae+BXxI0Sq4eLTQ/vnV1ReM4gRR12Vw 22 | anwZqHZ/WzBV27z9gW36t7wRxJS/uTXQ8J08KtBRBPv+19NXSqqjys5Jg0P1f+l9 23 | k+sWwpVqIF2rAQ3FyMfboaFKPC0jRn7iJMjp9KyvMbSI+25/rP5xvMicoJwRlk9I 24 | GNGasxSfmRpVpV+WG04xMGp3cPrCXHBdAAjI3O68YIPOX3VqZ6MasN1iGuYWOmam 25 | yeKzLUApYdtkR7yJ+X1FOKVfbzX27CLYmzwrHnDLJzu8NVgqLGU+qTSK0zm3sYE3 26 | w3ex6WX86Oz2QBJ5h/s2TLbsWis7ZkKjMyXqVWlbg4P3reyNrfpAoc0y1R9EjZlf 27 | 1c9HZBRBuRMgaPWmdSR4lxw1FhQBTstIfzC8lBYNbt8QRRtJIxVF9mxiL7H+6XH5 28 | FZXcQCHun6klGtCkypeAaviE 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /fixtures/certs/myclient.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIEojCCAooCAQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx 3 | ETAPBgNVBAcMCE1pbGxicmFlMRIwEAYDVQQKDAlMb2NhbCBEZXYxEjAQBgNVBAMM 4 | CWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALGjhpJf 5 | ej7btWCO4OCRJBliUAUyPMO8B649Qjn1Yiw9E1L5viByYJSihsfUQ7u2gHip7Qdi 6 | gKCA/s4+w8V2L0Dv8lCowCLkexf10b7XGQaOqhk2mlr/jOapAg0pKDoUlVErBBZK 7 | 2s6UbD/gLXAbxudwxCFKJ1Y7d/Dw5aTl1vlWZpHzf2o9/ZCeHXf8Xu3aMIEPJ79w 8 | G0vzNZK7bL1r1lQVzACdHAr34HAQAvgWB4ZjKqN8z0vGC0N0MpaAuHD8fH8wQ5Yi 9 | WBbDhDPFVzRYU8PcQjeZSMFqOulew9KVm+vXtcMvteEoXMXwWlqAGlvnv7sskc/V 10 | brLJJQaoswyKgy1QCKxVO47Ef2iU4kP75iDYx6NpApdnpN3zxHMHyZDxuwmtoKea 11 | lenxl5cZeHc6uUU1wXk+nmy7TrgW509mcopHzHj+Q0zyGUg/dRws3qXPAGZehJPo 12 | aYF1F54eiindF1yLMMH5osvy1bNp2EQezOlY3P4gqW9VHq3CQvytmDbXqS0vPzVA 13 | sFu8YazM3Bs0mW2bBXrEsajWDSjrvbhdZjlL9j2jqwZ2nzyan88M5t5T0vZhcu+w 14 | KisATI1yLdV3oWvLmdFz/XA9L6UyosTiwC1MWPmkOY4mcHn/Px70f40+wO815pZ6 15 | FbjecxRSyMfAm6vDPWtLAMUr1UoD4vasyvQNAgMBAAGgADANBgkqhkiG9w0BAQsF 16 | AAOCAgEAM9VLDurmvoYQyNEpRvFpOLPkgr8rgboo/HN+O/hN9jtmXranLxzTzd+u 17 | OJCujyzS3sbqiZwPeT3APHH4c/mLdrEKZHjfy2sEeXMsVW6dCOcIEYsADSCM6chi 18 | zU86aw4rAkd6YYB+lXFsEmBq78AIpw0vcdpoPoqGRG9ETQsjr4kD3ATGHTnaP551 19 | 61JJed7Kn5FTbieTmzmMa46dn7GjTTmPEcoAnHNCx4CbJAHwWEzvQWF4lVlyb2di 20 | jFD0NQ0WeaFHK/f6UQMqMq+7TpurN8sLWDlyPHA2X/FT+OsUMAX2mLcwZEsYhTjP 21 | dC4ZCuZ/itDgEp3hyPeKiLo+mL/bhhy50nzah/qclI9PS8ufUXEjWoObqiJ5eyIZ 22 | jTZ73qpLupS+Yrami98IYfuOotwGzKkVLwUPtCWQrKsun6YNtotuKKmqEEQX3Fm3 23 | ZXIYv0BckkXIGd0aKPeMGgMUO26pyxPBSRWB29F07LXzS6eEmfOHvZcT+QLZmys9 24 | FkH3yePeTilojCnxNINPyKT4Dk0NiZviCdKavUIJ5QtOyDJ1Nc9j5ss+QaAaNtZZ 25 | VTTjupNp+cfCh/kdyGpGP+GgXQQcGgw4OaIbfXqmec7RsqTOppK5gDR4Ne3e5FVm 26 | SpPDyHbv2GJolPG8/HCOsLCJED+wAEfhK/wUg8ZpC+7Ymct2TU8= 27 | -----END CERTIFICATE REQUEST----- 28 | -------------------------------------------------------------------------------- /fixtures/certs/myclient.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJJwIBAAKCAgEAsaOGkl96Ptu1YI7g4JEkGWJQBTI8w7wHrj1COfViLD0TUvm+ 3 | IHJglKKGx9RDu7aAeKntB2KAoID+zj7DxXYvQO/yUKjAIuR7F/XRvtcZBo6qGTaa 4 | Wv+M5qkCDSkoOhSVUSsEFkrazpRsP+AtcBvG53DEIUonVjt38PDlpOXW+VZmkfN/ 5 | aj39kJ4dd/xe7dowgQ8nv3AbS/M1krtsvWvWVBXMAJ0cCvfgcBAC+BYHhmMqo3zP 6 | S8YLQ3QyloC4cPx8fzBDliJYFsOEM8VXNFhTw9xCN5lIwWo66V7D0pWb69e1wy+1 7 | 4ShcxfBaWoAaW+e/uyyRz9VussklBqizDIqDLVAIrFU7jsR/aJTiQ/vmINjHo2kC 8 | l2ek3fPEcwfJkPG7Ca2gp5qV6fGXlxl4dzq5RTXBeT6ebLtOuBbnT2ZyikfMeP5D 9 | TPIZSD91HCzepc8AZl6Ek+hpgXUXnh6KKd0XXIswwfmiy/LVs2nYRB7M6Vjc/iCp 10 | b1UercJC/K2YNtepLS8/NUCwW7xhrMzcGzSZbZsFesSxqNYNKOu9uF1mOUv2PaOr 11 | BnafPJqfzwzm3lPS9mFy77AqKwBMjXIt1Xeha8uZ0XP9cD0vpTKixOLALUxY+aQ5 12 | jiZwef8/HvR/jT7A7zXmlnoVuN5zFFLIx8Cbq8M9a0sAxSvVSgPi9qzK9A0CAwEA 13 | AQKCAgAb4VyHsLCRGQ64nvQwitctnL6OcjoTRnm2ISs5yYelBdj4lvX+RbVe3rtk 14 | ta4D0jsLtS/cjts9VcGoQTWc0lXMTVysyC+Pymh/dDd9SmlFHDMaTfWf/qfws+n8 15 | gs8rfnuJB8VWcl0xOx5aUCcRh2qKfKprxyWxZRgIGucQIHrDG4pxsdP3qs8XWZmq 16 | cVO85RfjyaslYsUGAKAR7ZS9jiVPgTRJjF8QYaM6M2kj4uE/eGUCz94BOI4gAibG 17 | dGF+akJn+/0/nRhSSlF/hqOPNaXAAdvqugYvRSsF4be+X3jfZTXD8sMLGbil4Hlt 18 | 5tk8P31aNT6Vbhw3t1Y2W1fuyfaYbPZfprpR/6ZPV3Uf1oWoh1ystIxWnfU7Qdxu 19 | vrkHkrtho6Qt/7d8nNg0mQ8y5glNcVh/iNu9gkyHIpQ2dZpM9tpHArBGweHVDMvZ 20 | vrb/oJ5fRxnKkyouMtWvqO1TY4STPBwCDNSwJa0yxTn3fLvkOdHk1nGEKra7E7Nc 21 | hgsIe4q1ZoEikg7cZe8pvcsHIFfju3Kv/zgDTvHjzHPTdNear7mpk/LihlWdbTiI 22 | UKkgv17JHRsIhfE5G4pZXLRv2qjCGh+uS8yn2k5qPJqGoyIQ2A7BYbaQ/y2gVh6u 23 | hnVdKeETT2uUqIS3xHrV0U9grAeldPJu7bHRwSoJ+HUbp+D8QQKCAQEA4/5K0+Qq 24 | p4rpB+4FpPkMcM4yE1j0ASwHsKGMDPU70/t8yfzZBlpf5WNHTOWa8frnxOyazo8E 25 | sjm2Xw1RlMb21bFF0wjb3uhN2ak++0zIKMf5vWnM0bb2z7yzbcOJVxLzO9DmRUh0 26 | OXvHvbeKbW9KXHT3YKA2zjaw0mO1zl7sd7r028wYpD6owGtfzooyXwWCnByIQ3nM 27 | JFB7wFJGIg6Kbu2eJULrN1EaT1Ye0FUVmc4x55FLmZvkYziQ88e4UsjYdZ4R5EFi 28 | 2XULVI1RA+NPqDXkXmpIx3JnRRvaPc74QatGvDFwY8YeCAjfGFN5LiwFJ6Cz3/jf 29 | WjDLOhqoSiYQ2QKCAQEAx3W7uPE7WNQRsyu2QnsEDdlikgP0FJf3fFZOYurfsxp7 30 | iqTpNkR9pSWXftP4FBM/KRruoY5TucmPTfGPRS6sQWTfUkVOhrUpOLnuWjf2DJxH 31 | Qqb0wnT76WcAB4L5Gr//91a+w3dwAX5YhdTZLxEKgpm8ky290otCg3+AYOb/P3Ja 32 | V8RR8RQCNV1+y7peBgjj/mbYeVpxjTiZ5cq4cx2OU4rnup/k3HIg1Gw+qr0N9AUN 33 | 2WYOL+X0qaKffDa2ekv88H6mVnfRSReFIpteuV0XITwvMc0DbHdj6zEj1TSZMExu 34 | rDVe7eh2BeL1QxbuazRUgwZ+kfy2NUzPkB1SSwi8VQKCAQBs8K4qj0S+Z8avjlFO 35 | Id6K7EvLKN72zGYkRRzZeDiNMwbOsS22NmrJ/eUs3i1qYJxsYS4bcwUocCEvS/rm 36 | XyfEtf8KNppw6YmBbrh0dZzSt7MiibJfptBKNP17fkpau+hTdZ8CDfvTF806XsAb 37 | SGk8wnsNxaBKaqGU9iYCJSNSlpe3is9fc71IrEXMOAaXltdw5sVJkKI12+s121o9 38 | nbsSBCJj5ZTlCrDKpfj1TSKUKo13+9om3PGFY5sHkTAHBoc/tDcSXRfxllbCoP/M 39 | HsqKMq4bWyfJfWXRBN0EWagQINocxHbShfEFn8+SHRizMj+ITuaEJ7P5sYT6D5DI 40 | VWYJAoIBAEqaxdFiIYGTKN+sbOqm2phXhB/7bJM7WC1glsc29N8n+6ebEUPkEF7y 41 | FZ0xqavQmyJD2ZgCBV0LgBd2T9FfqLx4/3LlS37lSfrWyMlj/xsuZRUQH6KQYR0n 42 | EoK8wXH4+MPJ5WZ1SSa13GSKfYW2SQkaecdPJ54VypYm3ZzhKf3QRuxnGQMkKcNO 43 | KjwHhF2be7PPQg75/lkFH8MstRsRpgengA90+QRfh9oMdtAkEJECRvDW1F2kFIRS 44 | uHacfFp4C67koFDdViGRs5GDLcYFhL5ApaJp/WrXqT7yTWXU26uOGyM8fzpbZbHD 45 | 91rVu+3LUAUGK9ds/7Yl+cj8vqgkJ1UCggEAc0a5kmBREz/8rAWKnlCZrhBsxUUM 46 | tiIj32h6dVdFo5SsoyVTxdB394npw1DAsC8xdowrcm/zsYstB3IDMYlrBnCdRxTU 47 | Xu6RD3Jci0Qg1cfLQg5snlRnrNz12wygXcvTvW8cHsda8vO+FL1RgFdehDtYoyZr 48 | swcLLRAOOLTRXy1Xdbv+8vE6s5ryl3uAO+2Zwbmur3tRL+rhXE+Tb308jlt8g2NK 49 | WrRbc3c092aImdGcKmkMGqo6H+xnL9Jj7sR161uO5JJQjxcYbZ5PBmm3J5Z71cSY 50 | LR5snbYdxUy7WKE3yxAoWi+FMsoGf+O77+oHAcpXRaTDv0Enr/7rEku5Yw== 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /fixtures/certs/myclient.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-openapi/runtime/976f770e0b538dbe959f0fb9665d83c2099d3674/fixtures/certs/myclient.p12 -------------------------------------------------------------------------------- /fixtures/certs/serial: -------------------------------------------------------------------------------- 1 | A125911D49DAD094 2 | -------------------------------------------------------------------------------- /flagext/byte_size.go: -------------------------------------------------------------------------------- 1 | package flagext 2 | 3 | import ( 4 | "github.com/docker/go-units" 5 | ) 6 | 7 | // ByteSize used to pass byte sizes to a go-flags CLI 8 | type ByteSize int 9 | 10 | // MarshalFlag implements go-flags Marshaller interface 11 | func (b ByteSize) MarshalFlag() (string, error) { 12 | return units.HumanSize(float64(b)), nil 13 | } 14 | 15 | // UnmarshalFlag implements go-flags Unmarshaller interface 16 | func (b *ByteSize) UnmarshalFlag(value string) error { 17 | sz, err := units.FromHumanSize(value) 18 | if err != nil { 19 | return err 20 | } 21 | *b = ByteSize(int(sz)) 22 | return nil 23 | } 24 | 25 | // String method for a bytesize (pflag value and stringer interface) 26 | func (b ByteSize) String() string { 27 | return units.HumanSize(float64(b)) 28 | } 29 | 30 | // Set the value of this bytesize (pflag value interfaces) 31 | func (b *ByteSize) Set(value string) error { 32 | return b.UnmarshalFlag(value) 33 | } 34 | 35 | // Type returns the type of the pflag value (pflag value interface) 36 | func (b *ByteSize) Type() string { 37 | return "byte-size" 38 | } 39 | -------------------------------------------------------------------------------- /flagext/byte_size_test.go: -------------------------------------------------------------------------------- 1 | package flagext 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestMarshalBytesize(t *testing.T) { 11 | v, err := ByteSize(1024).MarshalFlag() 12 | require.NoError(t, err) 13 | assert.Equal(t, "1.024kB", v) 14 | } 15 | 16 | func TestStringBytesize(t *testing.T) { 17 | v := ByteSize(2048).String() 18 | assert.Equal(t, "2.048kB", v) 19 | } 20 | 21 | func TestUnmarshalBytesize(t *testing.T) { 22 | var b ByteSize 23 | err := b.UnmarshalFlag("notASize") 24 | require.Error(t, err) 25 | 26 | err = b.UnmarshalFlag("1MB") 27 | require.NoError(t, err) 28 | assert.Equal(t, ByteSize(1000000), b) 29 | } 30 | 31 | func TestSetBytesize(t *testing.T) { 32 | var b ByteSize 33 | err := b.Set("notASize") 34 | require.Error(t, err) 35 | 36 | err = b.Set("2MB") 37 | require.NoError(t, err) 38 | assert.Equal(t, ByteSize(2000000), b) 39 | } 40 | 41 | func TestTypeBytesize(t *testing.T) { 42 | var b ByteSize 43 | assert.Equal(t, "byte-size", b.Type()) 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-openapi/runtime 2 | 3 | require ( 4 | github.com/docker/go-units v0.5.0 5 | github.com/go-openapi/analysis v0.23.0 6 | github.com/go-openapi/errors v0.22.1 7 | github.com/go-openapi/loads v0.22.0 8 | github.com/go-openapi/spec v0.21.0 9 | github.com/go-openapi/strfmt v0.23.0 10 | github.com/go-openapi/swag v0.23.1 11 | github.com/go-openapi/validate v0.24.0 12 | github.com/opentracing/opentracing-go v1.2.0 13 | github.com/stretchr/testify v1.10.0 14 | go.opentelemetry.io/otel v1.24.0 15 | go.opentelemetry.io/otel/sdk v1.24.0 16 | go.opentelemetry.io/otel/trace v1.24.0 17 | golang.org/x/sync v0.11.0 18 | gopkg.in/yaml.v3 v3.0.1 19 | ) 20 | 21 | require ( 22 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/go-logr/logr v1.4.1 // indirect 25 | github.com/go-logr/stdr v1.2.2 // indirect 26 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 27 | github.com/go-openapi/jsonreference v0.21.0 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/josharian/intern v1.0.0 // indirect 30 | github.com/mailru/easyjson v0.9.0 // indirect 31 | github.com/mitchellh/mapstructure v1.5.0 // indirect 32 | github.com/oklog/ulid v1.3.1 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | go.mongodb.org/mongo-driver v1.14.0 // indirect 35 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 36 | golang.org/x/sys v0.17.0 // indirect 37 | ) 38 | 39 | go 1.20 40 | -------------------------------------------------------------------------------- /hack/gen-self-signed-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # generate CA 4 | openssl genrsa -out myCA.key 4096 5 | openssl req -x509 -new -key myCA.key -out myCA.crt -days 730 -subj /CN="Go Swagger" 6 | 7 | # generate server cert and key 8 | openssl genrsa -out mycert1.key 4096 9 | openssl req -new -out mycert1.req -key mycert1.key -subj /CN="goswagger.local" 10 | openssl x509 -req -in mycert1.req -out mycert1.crt -CAkey myCA.key -CA myCA.crt -days 365 -CAcreateserial -CAserial serial 11 | 12 | # generate client cert, key and bundle 13 | openssl genrsa -out myclient.key 4096 14 | openssl req -new -key myclient.key -out myclient.csr 15 | openssl x509 -req -days 730 -in myclient.csr -out myclient.crt -CAkey myCA.key -CA myCA.crt -days 365 -CAcreateserial -CAserial serial 16 | openssl pkcs12 -export -clcerts -in myclient.crt -inkey myclient.key -out myclient.p12 17 | -------------------------------------------------------------------------------- /headers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "mime" 19 | "net/http" 20 | 21 | "github.com/go-openapi/errors" 22 | ) 23 | 24 | // ContentType parses a content type header 25 | func ContentType(headers http.Header) (string, string, error) { 26 | ct := headers.Get(HeaderContentType) 27 | orig := ct 28 | if ct == "" { 29 | ct = DefaultMime 30 | } 31 | if ct == "" { 32 | return "", "", nil 33 | } 34 | 35 | mt, opts, err := mime.ParseMediaType(ct) 36 | if err != nil { 37 | return "", "", errors.NewParseError(HeaderContentType, "header", orig, err) 38 | } 39 | 40 | if cs, ok := opts[charsetKey]; ok { 41 | return mt, cs, nil 42 | } 43 | 44 | return mt, "", nil 45 | } 46 | -------------------------------------------------------------------------------- /headers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "mime" 19 | "net/http" 20 | "testing" 21 | 22 | "github.com/go-openapi/errors" 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | func TestParseContentType(t *testing.T) { 28 | _, _, reason1 := mime.ParseMediaType("application(") 29 | _, _, reason2 := mime.ParseMediaType("application/json;char*") 30 | data := []struct { 31 | hdr, mt, cs string 32 | err *errors.ParseError 33 | }{ 34 | {"application/json", "application/json", "", nil}, 35 | {"text/html; charset=utf-8", "text/html", "utf-8", nil}, 36 | {"text/html;charset=utf-8", "text/html", "utf-8", nil}, 37 | {"", "application/octet-stream", "", nil}, 38 | {"text/html; charset=utf-8", "text/html", "utf-8", nil}, 39 | {"application(", "", "", errors.NewParseError("Content-Type", "header", "application(", reason1)}, 40 | {"application/json;char*", "", "", errors.NewParseError("Content-Type", "header", "application/json;char*", reason2)}, 41 | } 42 | 43 | headers := http.Header(map[string][]string{}) 44 | for _, v := range data { 45 | if v.hdr != "" { 46 | headers.Set("Content-Type", v.hdr) 47 | } else { 48 | headers.Del("Content-Type") 49 | } 50 | ct, cs, err := ContentType(headers) 51 | if v.err == nil { 52 | require.NoError(t, err, "input: %q, err: %v", v.hdr, err) 53 | } else { 54 | require.Error(t, err, "input: %q", v.hdr) 55 | assert.IsType(t, &errors.ParseError{}, err, "input: %q", v.hdr) 56 | assert.Equal(t, v.err.Error(), err.Error(), "input: %q", v.hdr) 57 | } 58 | assert.Equal(t, v.mt, ct, "input: %q", v.hdr) 59 | assert.Equal(t, v.cs, cs, "input: %q", v.hdr) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "net/http" 21 | 22 | "github.com/go-openapi/strfmt" 23 | ) 24 | 25 | // OperationHandlerFunc an adapter for a function to the OperationHandler interface 26 | type OperationHandlerFunc func(interface{}) (interface{}, error) 27 | 28 | // Handle implements the operation handler interface 29 | func (s OperationHandlerFunc) Handle(data interface{}) (interface{}, error) { 30 | return s(data) 31 | } 32 | 33 | // OperationHandler a handler for a swagger operation 34 | type OperationHandler interface { 35 | Handle(interface{}) (interface{}, error) 36 | } 37 | 38 | // ConsumerFunc represents a function that can be used as a consumer 39 | type ConsumerFunc func(io.Reader, interface{}) error 40 | 41 | // Consume consumes the reader into the data parameter 42 | func (fn ConsumerFunc) Consume(reader io.Reader, data interface{}) error { 43 | return fn(reader, data) 44 | } 45 | 46 | // Consumer implementations know how to bind the values on the provided interface to 47 | // data provided by the request body 48 | type Consumer interface { 49 | // Consume performs the binding of request values 50 | Consume(io.Reader, interface{}) error 51 | } 52 | 53 | // ProducerFunc represents a function that can be used as a producer 54 | type ProducerFunc func(io.Writer, interface{}) error 55 | 56 | // Produce produces the response for the provided data 57 | func (f ProducerFunc) Produce(writer io.Writer, data interface{}) error { 58 | return f(writer, data) 59 | } 60 | 61 | // Producer implementations know how to turn the provided interface into a valid 62 | // HTTP response 63 | type Producer interface { 64 | // Produce writes to the http response 65 | Produce(io.Writer, interface{}) error 66 | } 67 | 68 | // AuthenticatorFunc turns a function into an authenticator 69 | type AuthenticatorFunc func(interface{}) (bool, interface{}, error) 70 | 71 | // Authenticate authenticates the request with the provided data 72 | func (f AuthenticatorFunc) Authenticate(params interface{}) (bool, interface{}, error) { 73 | return f(params) 74 | } 75 | 76 | // Authenticator represents an authentication strategy 77 | // implementations of Authenticator know how to authenticate the 78 | // request data and translate that into a valid principal object or an error 79 | type Authenticator interface { 80 | Authenticate(interface{}) (bool, interface{}, error) 81 | } 82 | 83 | // AuthorizerFunc turns a function into an authorizer 84 | type AuthorizerFunc func(*http.Request, interface{}) error 85 | 86 | // Authorize authorizes the processing of the request for the principal 87 | func (f AuthorizerFunc) Authorize(r *http.Request, principal interface{}) error { 88 | return f(r, principal) 89 | } 90 | 91 | // Authorizer represents an authorization strategy 92 | // implementations of Authorizer know how to authorize the principal object 93 | // using the request data and returns error if unauthorized 94 | type Authorizer interface { 95 | Authorize(*http.Request, interface{}) error 96 | } 97 | 98 | // Validatable types implementing this interface allow customizing their validation 99 | // this will be used instead of the reflective validation based on the spec document. 100 | // the implementations are assumed to have been generated by the swagger tool so they should 101 | // contain all the validations obtained from the spec 102 | type Validatable interface { 103 | Validate(strfmt.Registry) error 104 | } 105 | 106 | // ContextValidatable types implementing this interface allow customizing their validation 107 | // this will be used instead of the reflective validation based on the spec document. 108 | // the implementations are assumed to have been generated by the swagger tool so they should 109 | // contain all the context validations obtained from the spec 110 | type ContextValidatable interface { 111 | ContextValidate(context.Context, strfmt.Registry) error 112 | } 113 | -------------------------------------------------------------------------------- /internal/testing/data_test.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestInvalidJSON(t *testing.T) { 10 | require.NotEmpty(t, InvalidJSONMessage) 11 | } 12 | -------------------------------------------------------------------------------- /internal/testing/petstore/api_test.go: -------------------------------------------------------------------------------- 1 | package petstore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestAPI(t *testing.T) { 10 | doc, api := NewAPI(t) 11 | 12 | require.NotNil(t, doc) 13 | require.NotNil(t, api) 14 | } 15 | -------------------------------------------------------------------------------- /internal/testing/simplepetstore/api_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package simplepetstore 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "net/http" 21 | "net/http/httptest" 22 | "testing" 23 | 24 | "github.com/go-openapi/runtime" 25 | "github.com/stretchr/testify/assert" 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestSimplePetstoreSpec(t *testing.T) { 30 | handler, err := NewPetstore() 31 | require.NoError(t, err) 32 | 33 | // Serves swagger spec document 34 | r, err := runtime.JSONRequest(http.MethodGet, "/swagger.json", nil) 35 | require.NoError(t, err) 36 | r = r.WithContext(context.Background()) 37 | rw := httptest.NewRecorder() 38 | handler.ServeHTTP(rw, r) 39 | assert.Equal(t, http.StatusOK, rw.Code) 40 | assert.JSONEq(t, swaggerJSON, rw.Body.String()) 41 | } 42 | 43 | func TestSimplePetstoreAllPets(t *testing.T) { 44 | handler, err := NewPetstore() 45 | require.NoError(t, err) 46 | 47 | // Serves swagger spec document 48 | r, err := runtime.JSONRequest(http.MethodGet, "/api/pets", nil) 49 | require.NoError(t, err) 50 | r = r.WithContext(context.Background()) 51 | rw := httptest.NewRecorder() 52 | handler.ServeHTTP(rw, r) 53 | assert.Equal(t, http.StatusOK, rw.Code) 54 | assert.JSONEq(t, "[{\"id\":1,\"name\":\"Dog\",\"status\":\"available\"},{\"id\":2,\"name\":\"Cat\",\"status\":\"pending\"}]\n", rw.Body.String()) 55 | } 56 | 57 | func TestSimplePetstorePetByID(t *testing.T) { 58 | handler, err := NewPetstore() 59 | require.NoError(t, err) 60 | 61 | // Serves swagger spec document 62 | r, err := runtime.JSONRequest(http.MethodGet, "/api/pets/1", nil) 63 | require.NoError(t, err) 64 | r = r.WithContext(context.Background()) 65 | rw := httptest.NewRecorder() 66 | handler.ServeHTTP(rw, r) 67 | assert.Equal(t, http.StatusOK, rw.Code) 68 | assert.JSONEq(t, "{\"id\":1,\"name\":\"Dog\",\"status\":\"available\"}\n", rw.Body.String()) 69 | } 70 | 71 | func TestSimplePetstoreAddPet(t *testing.T) { 72 | handler, err := NewPetstore() 73 | require.NoError(t, err) 74 | 75 | // Serves swagger spec document 76 | r, err := runtime.JSONRequest(http.MethodPost, "/api/pets", bytes.NewBufferString(`{"name": "Fish","status": "available"}`)) 77 | require.NoError(t, err) 78 | r = r.WithContext(context.Background()) 79 | rw := httptest.NewRecorder() 80 | handler.ServeHTTP(rw, r) 81 | assert.Equal(t, http.StatusOK, rw.Code) 82 | assert.JSONEq(t, "{\"id\":3,\"name\":\"Fish\",\"status\":\"available\"}\n", rw.Body.String()) 83 | } 84 | 85 | func TestSimplePetstoreDeletePet(t *testing.T) { 86 | handler, err := NewPetstore() 87 | require.NoError(t, err) 88 | 89 | // Serves swagger spec document 90 | r, err := runtime.JSONRequest(http.MethodDelete, "/api/pets/1", nil) 91 | require.NoError(t, err) 92 | r = r.WithContext(context.Background()) 93 | rw := httptest.NewRecorder() 94 | handler.ServeHTTP(rw, r) 95 | assert.Equal(t, http.StatusNoContent, rw.Code) 96 | assert.Equal(t, "", rw.Body.String()) 97 | 98 | r, err = runtime.JSONRequest(http.MethodGet, "/api/pets/1", nil) 99 | require.NoError(t, err) 100 | r = r.WithContext(context.Background()) 101 | rw = httptest.NewRecorder() 102 | handler.ServeHTTP(rw, r) 103 | assert.Equal(t, http.StatusNotFound, rw.Code) 104 | assert.JSONEq(t, "{\"code\":404,\"message\":\"not found: pet 1\"}", rw.Body.String()) 105 | } 106 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "encoding/json" 19 | "io" 20 | ) 21 | 22 | // JSONConsumer creates a new JSON consumer 23 | func JSONConsumer() Consumer { 24 | return ConsumerFunc(func(reader io.Reader, data interface{}) error { 25 | dec := json.NewDecoder(reader) 26 | dec.UseNumber() // preserve number formats 27 | return dec.Decode(data) 28 | }) 29 | } 30 | 31 | // JSONProducer creates a new JSON producer 32 | func JSONProducer() Producer { 33 | return ProducerFunc(func(writer io.Writer, data interface{}) error { 34 | enc := json.NewEncoder(writer) 35 | enc.SetEscapeHTML(false) 36 | return enc.Encode(data) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /json_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | var consProdJSON = `{"name":"Somebody","id":1}` 28 | 29 | type eofRdr struct { 30 | } 31 | 32 | func (r *eofRdr) Read(_ []byte) (int, error) { 33 | return 0, io.EOF 34 | } 35 | 36 | func TestJSONConsumer(t *testing.T) { 37 | cons := JSONConsumer() 38 | var data struct { 39 | Name string 40 | ID int 41 | } 42 | err := cons.Consume(bytes.NewBufferString(consProdJSON), &data) 43 | require.NoError(t, err) 44 | assert.Equal(t, "Somebody", data.Name) 45 | assert.Equal(t, 1, data.ID) 46 | 47 | err = cons.Consume(new(eofRdr), &data) 48 | require.Error(t, err) 49 | } 50 | 51 | func TestJSONProducer(t *testing.T) { 52 | prod := JSONProducer() 53 | data := struct { 54 | Name string `json:"name"` 55 | ID int `json:"id"` 56 | }{Name: "Somebody", ID: 1} 57 | 58 | rw := httptest.NewRecorder() 59 | err := prod.Produce(rw, data) 60 | require.NoError(t, err) 61 | assert.Equal(t, consProdJSON+"\n", rw.Body.String()) 62 | } 63 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "os" 4 | 5 | type Logger interface { 6 | Printf(format string, args ...interface{}) 7 | Debugf(format string, args ...interface{}) 8 | } 9 | 10 | func DebugEnabled() bool { 11 | d := os.Getenv("SWAGGER_DEBUG") 12 | if d != "" && d != "false" && d != "0" { 13 | return true 14 | } 15 | d = os.Getenv("DEBUG") 16 | if d != "" && d != "false" && d != "0" { 17 | return true 18 | } 19 | return false 20 | } 21 | -------------------------------------------------------------------------------- /logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLogger(t *testing.T) { 10 | require.False(t, DebugEnabled()) 11 | } 12 | -------------------------------------------------------------------------------- /logger/standard.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | var _ Logger = StandardLogger{} 9 | 10 | type StandardLogger struct{} 11 | 12 | func (StandardLogger) Printf(format string, args ...interface{}) { 13 | if len(format) == 0 || format[len(format)-1] != '\n' { 14 | format += "\n" 15 | } 16 | fmt.Fprintf(os.Stderr, format, args...) 17 | } 18 | 19 | func (StandardLogger) Debugf(format string, args ...interface{}) { 20 | if len(format) == 0 || format[len(format)-1] != '\n' { 21 | format += "\n" 22 | } 23 | fmt.Fprintf(os.Stderr, format, args...) 24 | } 25 | -------------------------------------------------------------------------------- /middleware/body_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "path" 8 | "testing" 9 | 10 | "github.com/go-openapi/runtime" 11 | "github.com/go-openapi/runtime/internal/testing/petstore" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | type eofReader struct { 17 | } 18 | 19 | func (r *eofReader) Read(_ []byte) (int, error) { 20 | return 0, io.EOF 21 | } 22 | 23 | func (r *eofReader) Close() error { 24 | return nil 25 | } 26 | 27 | type rbn func(*http.Request, *MatchedRoute) error 28 | 29 | func (b rbn) BindRequest(r *http.Request, rr *MatchedRoute) error { 30 | return b(r, rr) 31 | } 32 | 33 | func TestBindRequest_BodyValidation(t *testing.T) { 34 | spec, api := petstore.NewAPI(t) 35 | ctx := NewContext(spec, api, nil) 36 | api.DefaultConsumes = runtime.JSONMime 37 | ctx.router = DefaultRouter(spec, ctx.api) 38 | 39 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, path.Join(spec.BasePath(), "/pets"), new(eofReader)) 40 | require.NoError(t, err) 41 | req.Header.Set("Content-Type", runtime.JSONMime) 42 | 43 | ri, rCtx, ok := ctx.RouteInfo(req) 44 | require.True(t, ok) 45 | req = rCtx 46 | 47 | err = ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, _ *MatchedRoute) error { 48 | defer r.Body.Close() 49 | var data interface{} 50 | e := runtime.JSONConsumer().Consume(r.Body, &data) 51 | _ = data 52 | return e 53 | })) 54 | require.Error(t, err) 55 | assert.Equal(t, io.EOF, err) 56 | } 57 | 58 | func TestBindRequest_DeleteNoBody(t *testing.T) { 59 | spec, api := petstore.NewAPI(t) 60 | ctx := NewContext(spec, api, nil) 61 | api.DefaultConsumes = runtime.JSONMime 62 | ctx.router = DefaultRouter(spec, ctx.api) 63 | 64 | req, err := http.NewRequestWithContext(context.Background(), http.MethodDelete, path.Join(spec.BasePath(), "/pets/123"), new(eofReader)) 65 | require.NoError(t, err) 66 | 67 | req.Header.Set("Accept", "*/*") 68 | ri, rCtx, ok := ctx.RouteInfo(req) 69 | require.True(t, ok) 70 | req = rCtx 71 | 72 | err = ctx.BindValidRequest(req, ri, rbn(func(_ *http.Request, _ *MatchedRoute) error { 73 | return nil 74 | })) 75 | require.NoError(t, err) 76 | // assert.Equal(t, io.EOF, bverr) 77 | 78 | req, err = http.NewRequestWithContext(context.Background(), http.MethodDelete, path.Join(spec.BasePath(), "/pets/123"), new(eofReader)) 79 | require.NoError(t, err) 80 | req.Header.Set("Accept", "*/*") 81 | req.Header.Set("Content-Type", runtime.JSONMime) 82 | req.ContentLength = 1 83 | 84 | ri, rCtx, ok = ctx.RouteInfo(req) 85 | require.True(t, ok) 86 | req = rCtx 87 | 88 | err = ctx.BindValidRequest(req, ri, rbn(func(r *http.Request, _ *MatchedRoute) error { 89 | defer r.Body.Close() 90 | var data interface{} 91 | e := runtime.JSONConsumer().Consume(r.Body, &data) 92 | _ = data 93 | return e 94 | })) 95 | require.Error(t, err) 96 | assert.Equal(t, io.EOF, err) 97 | } 98 | -------------------------------------------------------------------------------- /middleware/debug_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | stdcontext "context" 6 | "log" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | 11 | "github.com/go-openapi/runtime/internal/testing/petstore" 12 | "github.com/go-openapi/runtime/logger" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | type customLogger struct { 18 | logger.StandardLogger 19 | lg *log.Logger 20 | } 21 | 22 | func (l customLogger) Debugf(format string, args ...interface{}) { 23 | l.lg.Printf(format, args...) 24 | } 25 | 26 | func TestDebugMode(t *testing.T) { 27 | t.Run("with normal mode", func(t *testing.T) { 28 | t.Setenv("DEBUG", "") 29 | 30 | logFunc := debugLogfFunc(nil) 31 | require.NotNil(t, logFunc) 32 | }) 33 | 34 | t.Run("with debug mode", func(t *testing.T) { 35 | t.Setenv("DEBUG", "true") 36 | 37 | t.Run("debugLogFunc with nil logger yields standard logger", func(t *testing.T) { 38 | logFunc := debugLogfFunc(nil) 39 | require.NotNil(t, logFunc) 40 | }) 41 | t.Run("debugLogFunc with custom logger", func(t *testing.T) { 42 | var capture bytes.Buffer 43 | logger := customLogger{lg: log.New(&capture, "test", log.Lshortfile)} 44 | logFunc := debugLogfFunc(logger) 45 | require.NotNil(t, logFunc) 46 | 47 | logFunc("debug") 48 | assert.NotEmpty(t, capture.String()) 49 | }) 50 | }) 51 | } 52 | 53 | func TestDebugRouterOptions(t *testing.T) { 54 | t.Run("with normal mode", func(t *testing.T) { 55 | t.Setenv("DEBUG", "") 56 | 57 | t.Run("should capture debug from context & router", func(t *testing.T) { 58 | var capture bytes.Buffer 59 | logger := customLogger{lg: log.New(&capture, "test", log.Lshortfile)} 60 | 61 | t.Run("run some activiy", doCheckWithContext(logger)) 62 | assert.Empty(t, capture.String()) 63 | }) 64 | 65 | t.Run("should capture debug from standalone DefaultRouter", func(t *testing.T) { 66 | var capture bytes.Buffer 67 | logger := customLogger{lg: log.New(&capture, "test", log.Lshortfile)} 68 | 69 | t.Run("run some activiy", doCheckWithDefaultRouter(logger)) 70 | assert.Empty(t, capture.String()) 71 | }) 72 | }) 73 | 74 | t.Run("with debug mode", func(t *testing.T) { 75 | t.Setenv("DEBUG", "1") 76 | 77 | t.Run("should capture debug from context & router", func(t *testing.T) { 78 | var capture bytes.Buffer 79 | logger := customLogger{lg: log.New(&capture, "test", log.Lshortfile)} 80 | 81 | t.Run("run some activiy", doCheckWithContext(logger)) 82 | assert.NotEmpty(t, capture.String()) 83 | }) 84 | 85 | t.Run("should capture debug from standalone DefaultRouter", func(t *testing.T) { 86 | var capture bytes.Buffer 87 | logger := customLogger{lg: log.New(&capture, "test", log.Lshortfile)} 88 | 89 | t.Run("run some activiy", doCheckWithDefaultRouter(logger)) 90 | assert.NotEmpty(t, capture.String()) 91 | }) 92 | }) 93 | } 94 | 95 | func doCheckWithContext(logger logger.Logger) func(*testing.T) { 96 | return func(t *testing.T) { 97 | spec, api := petstore.NewAPI(t) 98 | context := NewContext(spec, api, nil) 99 | context.SetLogger(logger) 100 | mw := NewRouter(context, http.HandlerFunc(terminator)) 101 | 102 | recorder := httptest.NewRecorder() 103 | request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil) 104 | require.NoError(t, err) 105 | mw.ServeHTTP(recorder, request) 106 | assert.Equal(t, http.StatusOK, recorder.Code) 107 | } 108 | } 109 | 110 | func doCheckWithDefaultRouter(lg logger.Logger) func(*testing.T) { 111 | return func(t *testing.T) { 112 | spec, api := petstore.NewAPI(t) 113 | context := NewContext(spec, api, nil) 114 | context.SetLogger(lg) 115 | router := DefaultRouter( 116 | spec, 117 | newRoutableUntypedAPI(spec, api, new(Context)), 118 | WithDefaultRouterLogger(lg)) 119 | 120 | _ = router.OtherMethods("post", "/api/pets/{id}") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /middleware/denco/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Naoya Inada 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /middleware/denco/server.go: -------------------------------------------------------------------------------- 1 | package denco 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // Mux represents a multiplexer for HTTP request. 8 | type Mux struct{} 9 | 10 | // NewMux returns a new Mux. 11 | func NewMux() *Mux { 12 | return &Mux{} 13 | } 14 | 15 | // GET is shorthand of Mux.Handler("GET", path, handler). 16 | func (m *Mux) GET(path string, handler HandlerFunc) Handler { 17 | return m.Handler("GET", path, handler) 18 | } 19 | 20 | // POST is shorthand of Mux.Handler("POST", path, handler). 21 | func (m *Mux) POST(path string, handler HandlerFunc) Handler { 22 | return m.Handler("POST", path, handler) 23 | } 24 | 25 | // PUT is shorthand of Mux.Handler("PUT", path, handler). 26 | func (m *Mux) PUT(path string, handler HandlerFunc) Handler { 27 | return m.Handler("PUT", path, handler) 28 | } 29 | 30 | // HEAD is shorthand of Mux.Handler("HEAD", path, handler). 31 | func (m *Mux) HEAD(path string, handler HandlerFunc) Handler { 32 | return m.Handler("HEAD", path, handler) 33 | } 34 | 35 | // Handler returns a handler for HTTP method. 36 | func (m *Mux) Handler(method, path string, handler HandlerFunc) Handler { 37 | return Handler{ 38 | Method: method, 39 | Path: path, 40 | Func: handler, 41 | } 42 | } 43 | 44 | // Build builds a http.Handler. 45 | func (m *Mux) Build(handlers []Handler) (http.Handler, error) { 46 | recordMap := make(map[string][]Record) 47 | for _, h := range handlers { 48 | recordMap[h.Method] = append(recordMap[h.Method], NewRecord(h.Path, h.Func)) 49 | } 50 | mux := newServeMux() 51 | for m, records := range recordMap { 52 | router := New() 53 | if err := router.Build(records); err != nil { 54 | return nil, err 55 | } 56 | mux.routers[m] = router 57 | } 58 | return mux, nil 59 | } 60 | 61 | // Handler represents a handler of HTTP request. 62 | type Handler struct { 63 | // Method is an HTTP method. 64 | Method string 65 | 66 | // Path is a routing path for handler. 67 | Path string 68 | 69 | // Func is a function of handler of HTTP request. 70 | Func HandlerFunc 71 | } 72 | 73 | // The HandlerFunc type is aliased to type of handler function. 74 | type HandlerFunc func(w http.ResponseWriter, r *http.Request, params Params) 75 | 76 | type serveMux struct { 77 | routers map[string]*Router 78 | } 79 | 80 | func newServeMux() *serveMux { 81 | return &serveMux{ 82 | routers: make(map[string]*Router), 83 | } 84 | } 85 | 86 | // ServeHTTP implements http.Handler interface. 87 | func (mux *serveMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 88 | handler, params := mux.handler(r.Method, r.URL.Path) 89 | handler(w, r, params) 90 | } 91 | 92 | func (mux *serveMux) handler(method, path string) (HandlerFunc, []Param) { 93 | if router, found := mux.routers[method]; found { 94 | if handler, params, found := router.Lookup(path); found { 95 | return handler.(HandlerFunc), params 96 | } 97 | } 98 | return NotFound, nil 99 | } 100 | 101 | // NotFound replies to the request with an HTTP 404 not found error. 102 | // NotFound is called when unknown HTTP method or a handler not found. 103 | // If you want to use the your own NotFound handler, please overwrite this variable. 104 | var NotFound = func(w http.ResponseWriter, r *http.Request, _ Params) { 105 | http.NotFound(w, r) 106 | } 107 | -------------------------------------------------------------------------------- /middleware/denco/server_test.go: -------------------------------------------------------------------------------- 1 | package denco_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | 11 | "github.com/go-openapi/runtime/middleware/denco" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func testHandlerFunc(w http.ResponseWriter, r *http.Request, params denco.Params) { 17 | fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params) 18 | } 19 | 20 | func TestMux(t *testing.T) { 21 | mux := denco.NewMux() 22 | handler, err := mux.Build([]denco.Handler{ 23 | mux.GET("/", testHandlerFunc), 24 | mux.GET("/user/:name", testHandlerFunc), 25 | mux.POST("/user/:name", testHandlerFunc), 26 | mux.HEAD("/user/:name", testHandlerFunc), 27 | mux.PUT("/user/:name", testHandlerFunc), 28 | mux.Handler(http.MethodGet, "/user/handler", testHandlerFunc), 29 | mux.Handler(http.MethodPost, "/user/handler", testHandlerFunc), 30 | mux.Handler(http.MethodPut, "/user/inference", testHandlerFunc), 31 | }) 32 | require.NoError(t, err) 33 | 34 | server := httptest.NewServer(handler) 35 | defer server.Close() 36 | 37 | for _, v := range []struct { 38 | status int 39 | method, path, expected string 40 | }{ 41 | {http.StatusOK, http.MethodGet, "/", "method: GET, path: /, params: []"}, 42 | {http.StatusOK, http.MethodGet, "/user/alice", "method: GET, path: /user/alice, params: [{name alice}]"}, 43 | {http.StatusOK, http.MethodPost, "/user/bob", "method: POST, path: /user/bob, params: [{name bob}]"}, 44 | {http.StatusOK, http.MethodHead, "/user/alice", ""}, 45 | {http.StatusOK, http.MethodPut, "/user/bob", "method: PUT, path: /user/bob, params: [{name bob}]"}, 46 | {http.StatusNotFound, http.MethodPost, "/", "404 page not found\n"}, 47 | {http.StatusNotFound, http.MethodGet, "/unknown", "404 page not found\n"}, 48 | {http.StatusNotFound, http.MethodPost, "/user/alice/1", "404 page not found\n"}, 49 | {http.StatusOK, http.MethodGet, "/user/handler", "method: GET, path: /user/handler, params: []"}, 50 | {http.StatusOK, http.MethodPost, "/user/handler", "method: POST, path: /user/handler, params: []"}, 51 | {http.StatusOK, http.MethodPut, "/user/inference", "method: PUT, path: /user/inference, params: []"}, 52 | } { 53 | req, err := http.NewRequestWithContext(context.Background(), v.method, server.URL+v.path, nil) 54 | require.NoError(t, err) 55 | 56 | res, err := http.DefaultClient.Do(req) 57 | require.NoError(t, err) 58 | 59 | defer res.Body.Close() 60 | body, err := io.ReadAll(res.Body) 61 | require.NoError(t, err) 62 | 63 | actual := string(body) 64 | expected := v.expected 65 | 66 | assert.Equalf(t, v.status, res.StatusCode, "for method %s in path %s", v.method, v.path) 67 | assert.Equalf(t, expected, actual, "for method %s in path %s", v.method, v.path) 68 | } 69 | } 70 | 71 | func TestNotFound(t *testing.T) { 72 | mux := denco.NewMux() 73 | handler, err := mux.Build([]denco.Handler{}) 74 | require.NoError(t, err) 75 | 76 | server := httptest.NewServer(handler) 77 | defer server.Close() 78 | 79 | origNotFound := denco.NotFound 80 | defer func() { 81 | denco.NotFound = origNotFound 82 | }() 83 | denco.NotFound = func(w http.ResponseWriter, r *http.Request, params denco.Params) { 84 | w.WriteHeader(http.StatusServiceUnavailable) 85 | fmt.Fprintf(w, "method: %s, path: %s, params: %v", r.Method, r.URL.Path, params) 86 | } 87 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL, nil) 88 | require.NoError(t, err) 89 | res, err := http.DefaultClient.Do(req) 90 | require.NoError(t, err) 91 | 92 | defer res.Body.Close() 93 | body, err := io.ReadAll(res.Body) 94 | require.NoError(t, err) 95 | 96 | actual := string(body) 97 | expected := "method: GET, path: /, params: []" 98 | 99 | assert.Equal(t, http.StatusServiceUnavailable, res.StatusCode) 100 | assert.Equal(t, expected, actual) 101 | } 102 | -------------------------------------------------------------------------------- /middleware/denco/util.go: -------------------------------------------------------------------------------- 1 | package denco 2 | 3 | // NextSeparator returns an index of next separator in path. 4 | func NextSeparator(path string, start int) int { 5 | for start < len(path) { 6 | if c := path[start]; c == '/' || c == TerminationCharacter { 7 | break 8 | } 9 | start++ 10 | } 11 | return start 12 | } 13 | -------------------------------------------------------------------------------- /middleware/denco/util_test.go: -------------------------------------------------------------------------------- 1 | package denco_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/go-openapi/runtime/middleware/denco" 8 | ) 9 | 10 | func TestNextSeparator(t *testing.T) { 11 | for _, testcase := range []struct { 12 | path string 13 | start int 14 | expected interface{} 15 | }{ 16 | {"/path/to/route", 0, 0}, 17 | {"/path/to/route", 1, 5}, 18 | {"/path/to/route", 9, 14}, 19 | {"/path.html", 1, 10}, 20 | {"/foo/bar.html", 1, 4}, 21 | {"/foo/bar.html/baz.png", 5, 13}, 22 | {"/foo/bar.html/baz.png", 14, 21}, 23 | {"path#", 0, 4}, 24 | } { 25 | actual := denco.NextSeparator(testcase.path, testcase.start) 26 | expected := testcase.expected 27 | if !reflect.DeepEqual(actual, expected) { 28 | t.Errorf("path = %q, start = %v expect %v, but %v", testcase.path, testcase.start, expected, actual) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /middleware/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package middleware provides the library with helper functions for serving swagger APIs. 17 | 18 | Pseudo middleware handler 19 | 20 | import ( 21 | "net/http" 22 | 23 | "github.com/go-openapi/errors" 24 | ) 25 | 26 | func newCompleteMiddleware(ctx *Context) http.Handler { 27 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 28 | // use context to lookup routes 29 | if matched, ok := ctx.RouteInfo(r); ok { 30 | 31 | if matched.NeedsAuth() { 32 | if _, err := ctx.Authorize(r, matched); err != nil { 33 | ctx.Respond(rw, r, matched.Produces, matched, err) 34 | return 35 | } 36 | } 37 | 38 | bound, validation := ctx.BindAndValidate(r, matched) 39 | if validation != nil { 40 | ctx.Respond(rw, r, matched.Produces, matched, validation) 41 | return 42 | } 43 | 44 | result, err := matched.Handler.Handle(bound) 45 | if err != nil { 46 | ctx.Respond(rw, r, matched.Produces, matched, err) 47 | return 48 | } 49 | 50 | ctx.Respond(rw, r, matched.Produces, matched, result) 51 | return 52 | } 53 | 54 | // Not found, check if it exists in the other methods first 55 | if others := ctx.AllowedMethods(r); len(others) > 0 { 56 | ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others)) 57 | return 58 | } 59 | ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path)) 60 | }) 61 | } 62 | */ 63 | package middleware 64 | -------------------------------------------------------------------------------- /middleware/header/header_test.go: -------------------------------------------------------------------------------- 1 | package header 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestHeader(t *testing.T) { 11 | hdr := http.Header{ 12 | "x-test": []string{"value"}, 13 | } 14 | clone := Copy(hdr) 15 | require.Len(t, clone, len(hdr)) 16 | } 17 | -------------------------------------------------------------------------------- /middleware/negotiate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd. 6 | 7 | // this file was taken from the github.com/golang/gddo repository 8 | 9 | package middleware 10 | 11 | import ( 12 | "net/http" 13 | "strings" 14 | 15 | "github.com/go-openapi/runtime/middleware/header" 16 | ) 17 | 18 | // NegotiateContentEncoding returns the best offered content encoding for the 19 | // request's Accept-Encoding header. If two offers match with equal weight and 20 | // then the offer earlier in the list is preferred. If no offers are 21 | // acceptable, then "" is returned. 22 | func NegotiateContentEncoding(r *http.Request, offers []string) string { 23 | bestOffer := "identity" 24 | bestQ := -1.0 25 | specs := header.ParseAccept(r.Header, "Accept-Encoding") 26 | for _, offer := range offers { 27 | for _, spec := range specs { 28 | if spec.Q > bestQ && 29 | (spec.Value == "*" || spec.Value == offer) { 30 | bestQ = spec.Q 31 | bestOffer = offer 32 | } 33 | } 34 | } 35 | if bestQ == 0 { 36 | bestOffer = "" 37 | } 38 | return bestOffer 39 | } 40 | 41 | // NegotiateContentType returns the best offered content type for the request's 42 | // Accept header. If two offers match with equal weight, then the more specific 43 | // offer is preferred. For example, text/* trumps */*. If two offers match 44 | // with equal weight and specificity, then the offer earlier in the list is 45 | // preferred. If no offers match, then defaultOffer is returned. 46 | func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string { 47 | bestOffer := defaultOffer 48 | bestQ := -1.0 49 | bestWild := 3 50 | specs := header.ParseAccept(r.Header, "Accept") 51 | for _, rawOffer := range offers { 52 | offer := normalizeOffer(rawOffer) 53 | // No Accept header: just return the first offer. 54 | if len(specs) == 0 { 55 | return rawOffer 56 | } 57 | for _, spec := range specs { 58 | switch { 59 | case spec.Q == 0.0: 60 | // ignore 61 | case spec.Q < bestQ: 62 | // better match found 63 | case spec.Value == "*/*": 64 | if spec.Q > bestQ || bestWild > 2 { 65 | bestQ = spec.Q 66 | bestWild = 2 67 | bestOffer = rawOffer 68 | } 69 | case strings.HasSuffix(spec.Value, "/*"): 70 | if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) && 71 | (spec.Q > bestQ || bestWild > 1) { 72 | bestQ = spec.Q 73 | bestWild = 1 74 | bestOffer = rawOffer 75 | } 76 | default: 77 | if spec.Value == offer && 78 | (spec.Q > bestQ || bestWild > 0) { 79 | bestQ = spec.Q 80 | bestWild = 0 81 | bestOffer = rawOffer 82 | } 83 | } 84 | } 85 | } 86 | return bestOffer 87 | } 88 | 89 | func normalizeOffers(orig []string) (norm []string) { 90 | for _, o := range orig { 91 | norm = append(norm, normalizeOffer(o)) 92 | } 93 | return 94 | } 95 | 96 | func normalizeOffer(orig string) string { 97 | const maxParts = 2 98 | return strings.SplitN(orig, ";", maxParts)[0] 99 | } 100 | -------------------------------------------------------------------------------- /middleware/negotiate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd. 6 | 7 | package middleware 8 | 9 | import ( 10 | "net/http" 11 | "testing" 12 | ) 13 | 14 | var negotiateContentEncodingTests = []struct { 15 | s string 16 | offers []string 17 | expect string 18 | }{ 19 | {"", []string{"identity", "gzip"}, "identity"}, 20 | {"*;q=0", []string{"identity", "gzip"}, ""}, 21 | {"gzip", []string{"identity", "gzip"}, "gzip"}, 22 | } 23 | 24 | func TestNegotiateContentEnoding(t *testing.T) { 25 | for _, tt := range negotiateContentEncodingTests { 26 | r := &http.Request{Header: http.Header{"Accept-Encoding": {tt.s}}} 27 | actual := NegotiateContentEncoding(r, tt.offers) 28 | if actual != tt.expect { 29 | t.Errorf("NegotiateContentEncoding(%q, %#v)=%q, want %q", tt.s, tt.offers, actual, tt.expect) 30 | } 31 | } 32 | } 33 | 34 | var negotiateContentTypeTests = []struct { 35 | s string 36 | offers []string 37 | defaultOffer string 38 | expect string 39 | }{ 40 | {"text/html, */*;q=0", []string{"x/y"}, "", ""}, 41 | {"text/html, */*", []string{"x/y"}, "", "x/y"}, 42 | {"text/html, image/png", []string{"text/html", "image/png"}, "", "text/html"}, 43 | {"text/html, image/png", []string{"image/png", "text/html"}, "", "image/png"}, 44 | {"text/html, image/png; q=0.5", []string{"image/png"}, "", "image/png"}, 45 | {"text/html, image/png; q=0.5", []string{"text/html"}, "", "text/html"}, 46 | {"text/html, image/png; q=0.5", []string{"foo/bar"}, "", ""}, 47 | {"text/html, image/png; q=0.5", []string{"image/png", "text/html"}, "", "text/html"}, 48 | {"text/html, image/png; q=0.5", []string{"text/html", "image/png"}, "", "text/html"}, 49 | {"text/html;q=0.5, image/png", []string{"image/png"}, "", "image/png"}, 50 | {"text/html;q=0.5, image/png", []string{"text/html"}, "", "text/html"}, 51 | {"text/html;q=0.5, image/png", []string{"image/png", "text/html"}, "", "image/png"}, 52 | {"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"}, 53 | {"text/html;q=0.5, image/png", []string{"text/html", "image/png"}, "", "image/png"}, 54 | {"image/png, image/*;q=0.5", []string{"image/jpg", "image/png"}, "", "image/png"}, 55 | {"image/png, image/*;q=0.5", []string{"image/jpg"}, "", "image/jpg"}, 56 | {"image/png, image/*;q=0.5", []string{"image/jpg", "image/gif"}, "", "image/jpg"}, 57 | {"image/png, image/*", []string{"image/jpg", "image/gif"}, "", "image/jpg"}, 58 | {"image/png, image/*", []string{"image/gif", "image/jpg"}, "", "image/gif"}, 59 | {"image/png, image/*", []string{"image/gif", "image/png"}, "", "image/png"}, 60 | {"image/png, image/*", []string{"image/png", "image/gif"}, "", "image/png"}, 61 | {"application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=0.0.4;q=0.3", []string{"text/plain"}, "", "text/plain"}, 62 | {"application/json", []string{"application/json; charset=utf-8", "image/png"}, "", "application/json; charset=utf-8"}, 63 | {"application/json; charset=utf-8", []string{"application/json; charset=utf-8", "image/png"}, "", "application/json; charset=utf-8"}, 64 | {"application/json", []string{"application/vnd.cia.v1+json"}, "", ""}, 65 | // Default header of java clients 66 | {"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2", []string{"application/json"}, "", "application/json"}, 67 | } 68 | 69 | func TestNegotiateContentType(t *testing.T) { 70 | for _, tt := range negotiateContentTypeTests { 71 | r := &http.Request{Header: http.Header{"Accept": {tt.s}}} 72 | actual := NegotiateContentType(r, tt.offers, tt.defaultOffer) 73 | if actual != tt.expect { 74 | t.Errorf("NegotiateContentType(%q, %#v, %q)=%q, want %q", tt.s, tt.offers, tt.defaultOffer, actual, tt.expect) 75 | } 76 | } 77 | } 78 | 79 | func TestNegotiateContentTypeNoAcceptHeader(t *testing.T) { 80 | r := &http.Request{Header: http.Header{}} 81 | offers := []string{"application/json", "text/xml"} 82 | actual := NegotiateContentType(r, offers, "") 83 | if actual != "application/json" { 84 | t.Errorf("NegotiateContentType(empty, %#v, empty)=%q, want %q", offers, actual, "application/json") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /middleware/not_implemented.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/go-openapi/runtime" 21 | ) 22 | 23 | type errorResp struct { 24 | code int 25 | response interface{} 26 | headers http.Header 27 | } 28 | 29 | func (e *errorResp) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { 30 | for k, v := range e.headers { 31 | for _, val := range v { 32 | rw.Header().Add(k, val) 33 | } 34 | } 35 | if e.code > 0 { 36 | rw.WriteHeader(e.code) 37 | } else { 38 | rw.WriteHeader(http.StatusInternalServerError) 39 | } 40 | if err := producer.Produce(rw, e.response); err != nil { 41 | Logger.Printf("failed to write error response: %v", err) 42 | } 43 | } 44 | 45 | // NotImplemented the error response when the response is not implemented 46 | func NotImplemented(message string) Responder { 47 | return Error(http.StatusNotImplemented, message) 48 | } 49 | 50 | // Error creates a generic responder for returning errors, the data will be serialized 51 | // with the matching producer for the request 52 | func Error(code int, data interface{}, headers ...http.Header) Responder { 53 | var hdr http.Header 54 | for _, h := range headers { 55 | for k, v := range h { 56 | if hdr == nil { 57 | hdr = make(http.Header) 58 | } 59 | hdr[k] = v 60 | } 61 | } 62 | return &errorResp{ 63 | code: code, 64 | response: data, 65 | headers: hdr, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /middleware/not_implemented_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/go-openapi/runtime" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestErrorResponder(t *testing.T) { 13 | resp := Error(http.StatusBadRequest, map[string]string{"message": "this is the error body"}) 14 | 15 | rec := httptest.NewRecorder() 16 | resp.WriteResponse(rec, runtime.JSONProducer()) 17 | 18 | require.Equal(t, http.StatusBadRequest, rec.Code) 19 | require.JSONEq(t, "{\"message\":\"this is the error body\"}\n", rec.Body.String()) 20 | } 21 | -------------------------------------------------------------------------------- /middleware/operation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import "net/http" 18 | 19 | // NewOperationExecutor creates a context aware middleware that handles the operations after routing 20 | func NewOperationExecutor(ctx *Context) http.Handler { 21 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 22 | // use context to lookup routes 23 | route, rCtx, _ := ctx.RouteInfo(r) 24 | if rCtx != nil { 25 | r = rCtx 26 | } 27 | 28 | route.Handler.ServeHTTP(rw, r) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /middleware/operation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | stdcontext "context" 19 | "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/go-openapi/errors" 24 | "github.com/go-openapi/runtime" 25 | "github.com/go-openapi/runtime/internal/testing/petstore" 26 | "github.com/stretchr/testify/assert" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestOperationExecutor(t *testing.T) { 31 | spec, api := petstore.NewAPI(t) 32 | api.RegisterOperation("get", "/pets", runtime.OperationHandlerFunc(func(_ interface{}) (interface{}, error) { 33 | return []interface{}{ 34 | map[string]interface{}{"id": 1, "name": "a dog"}, 35 | }, nil 36 | })) 37 | 38 | context := NewContext(spec, api, nil) 39 | context.router = DefaultRouter(spec, context.api) 40 | mw := NewOperationExecutor(context) 41 | 42 | recorder := httptest.NewRecorder() 43 | request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil) 44 | require.NoError(t, err) 45 | request.Header.Add("Accept", "application/json") 46 | request.SetBasicAuth("admin", "admin") 47 | mw.ServeHTTP(recorder, request) 48 | assert.Equal(t, http.StatusOK, recorder.Code) 49 | assert.JSONEq(t, `[{"id":1,"name":"a dog"}]`+"\n", recorder.Body.String()) 50 | 51 | spec, api = petstore.NewAPI(t) 52 | api.RegisterOperation("get", "/pets", runtime.OperationHandlerFunc(func(_ interface{}) (interface{}, error) { 53 | return nil, errors.New(http.StatusUnprocessableEntity, "expected") 54 | })) 55 | 56 | context = NewContext(spec, api, nil) 57 | context.router = DefaultRouter(spec, context.api) 58 | mw = NewOperationExecutor(context) 59 | 60 | recorder = httptest.NewRecorder() 61 | request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil) 62 | require.NoError(t, err) 63 | request.Header.Add("Accept", "application/json") 64 | request.SetBasicAuth("admin", "admin") 65 | mw.ServeHTTP(recorder, request) 66 | assert.Equal(t, http.StatusUnprocessableEntity, recorder.Code) 67 | assert.JSONEq(t, `{"code":422,"message":"expected"}`, recorder.Body.String()) 68 | } 69 | -------------------------------------------------------------------------------- /middleware/rapidoc.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | "path" 9 | ) 10 | 11 | // RapiDocOpts configures the RapiDoc middlewares 12 | type RapiDocOpts struct { 13 | // BasePath for the UI, defaults to: / 14 | BasePath string 15 | 16 | // Path combines with BasePath to construct the path to the UI, defaults to: "docs". 17 | Path string 18 | 19 | // SpecURL is the URL of the spec document. 20 | // 21 | // Defaults to: /swagger.json 22 | SpecURL string 23 | 24 | // Title for the documentation site, default to: API documentation 25 | Title string 26 | 27 | // Template specifies a custom template to serve the UI 28 | Template string 29 | 30 | // RapiDocURL points to the js asset that generates the rapidoc site. 31 | // 32 | // Defaults to https://unpkg.com/rapidoc/dist/rapidoc-min.js 33 | RapiDocURL string 34 | } 35 | 36 | func (r *RapiDocOpts) EnsureDefaults() { 37 | common := toCommonUIOptions(r) 38 | common.EnsureDefaults() 39 | fromCommonToAnyOptions(common, r) 40 | 41 | // rapidoc-specifics 42 | if r.RapiDocURL == "" { 43 | r.RapiDocURL = rapidocLatest 44 | } 45 | if r.Template == "" { 46 | r.Template = rapidocTemplate 47 | } 48 | } 49 | 50 | // RapiDoc creates a middleware to serve a documentation site for a swagger spec. 51 | // 52 | // This allows for altering the spec before starting the http listener. 53 | func RapiDoc(opts RapiDocOpts, next http.Handler) http.Handler { 54 | opts.EnsureDefaults() 55 | 56 | pth := path.Join(opts.BasePath, opts.Path) 57 | tmpl := template.Must(template.New("rapidoc").Parse(opts.Template)) 58 | assets := bytes.NewBuffer(nil) 59 | if err := tmpl.Execute(assets, opts); err != nil { 60 | panic(fmt.Errorf("cannot execute template: %w", err)) 61 | } 62 | 63 | return serveUI(pth, assets.Bytes(), next) 64 | } 65 | 66 | const ( 67 | rapidocLatest = "https://unpkg.com/rapidoc/dist/rapidoc-min.js" 68 | rapidocTemplate = ` 69 | 70 | 71 | {{ .Title }} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ` 80 | ) 81 | -------------------------------------------------------------------------------- /middleware/rapidoc_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestRapiDocMiddleware(t *testing.T) { 15 | t.Run("with defaults", func(t *testing.T) { 16 | rapidoc := RapiDoc(RapiDocOpts{}, nil) 17 | 18 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/docs", nil) 19 | require.NoError(t, err) 20 | recorder := httptest.NewRecorder() 21 | rapidoc.ServeHTTP(recorder, req) 22 | assert.Equal(t, http.StatusOK, recorder.Code) 23 | assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get(contentTypeHeader)) 24 | var o RapiDocOpts 25 | o.EnsureDefaults() 26 | assert.Contains(t, recorder.Body.String(), fmt.Sprintf("%s", o.Title)) 27 | assert.Contains(t, recorder.Body.String(), fmt.Sprintf("", o.SpecURL)) 28 | assert.Contains(t, recorder.Body.String(), rapidocLatest) 29 | }) 30 | 31 | t.Run("edge cases", func(t *testing.T) { 32 | t.Run("with custom template that fails to execute", func(t *testing.T) { 33 | assert.Panics(t, func() { 34 | RapiDoc(RapiDocOpts{ 35 | Template: ` 36 | 37 | spec-url='{{ .Unknown }}' 38 | 39 | `, 40 | }, nil) 41 | }) 42 | }) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /middleware/redoc.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | "path" 9 | ) 10 | 11 | // RedocOpts configures the Redoc middlewares 12 | type RedocOpts struct { 13 | // BasePath for the UI, defaults to: / 14 | BasePath string 15 | 16 | // Path combines with BasePath to construct the path to the UI, defaults to: "docs". 17 | Path string 18 | 19 | // SpecURL is the URL of the spec document. 20 | // 21 | // Defaults to: /swagger.json 22 | SpecURL string 23 | 24 | // Title for the documentation site, default to: API documentation 25 | Title string 26 | 27 | // Template specifies a custom template to serve the UI 28 | Template string 29 | 30 | // RedocURL points to the js that generates the redoc site. 31 | // 32 | // Defaults to: https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js 33 | RedocURL string 34 | } 35 | 36 | // EnsureDefaults in case some options are missing 37 | func (r *RedocOpts) EnsureDefaults() { 38 | common := toCommonUIOptions(r) 39 | common.EnsureDefaults() 40 | fromCommonToAnyOptions(common, r) 41 | 42 | // redoc-specifics 43 | if r.RedocURL == "" { 44 | r.RedocURL = redocLatest 45 | } 46 | if r.Template == "" { 47 | r.Template = redocTemplate 48 | } 49 | } 50 | 51 | // Redoc creates a middleware to serve a documentation site for a swagger spec. 52 | // 53 | // This allows for altering the spec before starting the http listener. 54 | func Redoc(opts RedocOpts, next http.Handler) http.Handler { 55 | opts.EnsureDefaults() 56 | 57 | pth := path.Join(opts.BasePath, opts.Path) 58 | tmpl := template.Must(template.New("redoc").Parse(opts.Template)) 59 | assets := bytes.NewBuffer(nil) 60 | if err := tmpl.Execute(assets, opts); err != nil { 61 | panic(fmt.Errorf("cannot execute template: %w", err)) 62 | } 63 | 64 | return serveUI(pth, assets.Bytes(), next) 65 | } 66 | 67 | const ( 68 | redocLatest = "https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js" 69 | redocTemplate = ` 70 | 71 | 72 | {{ .Title }} 73 | 74 | 75 | 76 | 77 | 78 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ` 94 | ) 95 | -------------------------------------------------------------------------------- /middleware/redoc_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestRedocMiddleware(t *testing.T) { 15 | t.Run("with defaults", func(t *testing.T) { 16 | redoc := Redoc(RedocOpts{}, nil) 17 | 18 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/docs", nil) 19 | require.NoError(t, err) 20 | recorder := httptest.NewRecorder() 21 | redoc.ServeHTTP(recorder, req) 22 | assert.Equal(t, http.StatusOK, recorder.Code) 23 | assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get(contentTypeHeader)) 24 | var o RedocOpts 25 | o.EnsureDefaults() 26 | assert.Contains(t, recorder.Body.String(), fmt.Sprintf("%s", o.Title)) 27 | assert.Contains(t, recorder.Body.String(), fmt.Sprintf("", o.SpecURL)) 28 | assert.Contains(t, recorder.Body.String(), redocLatest) 29 | }) 30 | 31 | t.Run("with alternate path and spec URL", func(t *testing.T) { 32 | redoc := Redoc(RedocOpts{ 33 | BasePath: "/base", 34 | Path: "ui", 35 | SpecURL: "/ui/swagger.json", 36 | }, nil) 37 | 38 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/base/ui", nil) 39 | require.NoError(t, err) 40 | recorder := httptest.NewRecorder() 41 | redoc.ServeHTTP(recorder, req) 42 | assert.Equal(t, http.StatusOK, recorder.Code) 43 | assert.Contains(t, recorder.Body.String(), "") 44 | }) 45 | 46 | t.Run("with custom template", func(t *testing.T) { 47 | redoc := Redoc(RedocOpts{ 48 | Template: ` 49 | 50 | 51 | {{ .Title }} 52 | 53 | 54 | 55 | 56 | 59 | 65 | 66 | 67 | 76 | 77 | 78 | 79 | `, 80 | }, nil) 81 | 82 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/docs", nil) 83 | require.NoError(t, err) 84 | recorder := httptest.NewRecorder() 85 | redoc.ServeHTTP(recorder, req) 86 | assert.Equal(t, http.StatusOK, recorder.Code) 87 | assert.Contains(t, recorder.Body.String(), "required-props-first=true") 88 | }) 89 | 90 | t.Run("edge cases", func(t *testing.T) { 91 | t.Run("with invalid custom template", func(t *testing.T) { 92 | assert.Panics(t, func() { 93 | Redoc(RedocOpts{ 94 | Template: ` 95 | 96 | 97 | spec-url='{{ .Spec 98 | 99 | `, 100 | }, nil) 101 | }) 102 | }) 103 | 104 | t.Run("with custom template that fails to execute", func(t *testing.T) { 105 | assert.Panics(t, func() { 106 | Redoc(RedocOpts{ 107 | Template: ` 108 | 109 | spec-url='{{ .Unknown }}' 110 | 111 | `, 112 | }, nil) 113 | }) 114 | }) 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /middleware/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http" 19 | "reflect" 20 | 21 | "github.com/go-openapi/errors" 22 | "github.com/go-openapi/runtime" 23 | "github.com/go-openapi/runtime/logger" 24 | "github.com/go-openapi/spec" 25 | "github.com/go-openapi/strfmt" 26 | ) 27 | 28 | // UntypedRequestBinder binds and validates the data from a http request 29 | type UntypedRequestBinder struct { 30 | Spec *spec.Swagger 31 | Parameters map[string]spec.Parameter 32 | Formats strfmt.Registry 33 | paramBinders map[string]*untypedParamBinder 34 | debugLogf func(string, ...any) // a logging function to debug context and all components using it 35 | } 36 | 37 | // NewUntypedRequestBinder creates a new binder for reading a request. 38 | func NewUntypedRequestBinder(parameters map[string]spec.Parameter, spec *spec.Swagger, formats strfmt.Registry) *UntypedRequestBinder { 39 | binders := make(map[string]*untypedParamBinder) 40 | for fieldName, param := range parameters { 41 | binders[fieldName] = newUntypedParamBinder(param, spec, formats) 42 | } 43 | return &UntypedRequestBinder{ 44 | Parameters: parameters, 45 | paramBinders: binders, 46 | Spec: spec, 47 | Formats: formats, 48 | debugLogf: debugLogfFunc(nil), 49 | } 50 | } 51 | 52 | // Bind perform the databinding and validation 53 | func (o *UntypedRequestBinder) Bind(request *http.Request, routeParams RouteParams, consumer runtime.Consumer, data interface{}) error { 54 | val := reflect.Indirect(reflect.ValueOf(data)) 55 | isMap := val.Kind() == reflect.Map 56 | var result []error 57 | o.debugLogf("binding %d parameters for %s %s", len(o.Parameters), request.Method, request.URL.EscapedPath()) 58 | for fieldName, param := range o.Parameters { 59 | binder := o.paramBinders[fieldName] 60 | o.debugLogf("binding parameter %s for %s %s", fieldName, request.Method, request.URL.EscapedPath()) 61 | var target reflect.Value 62 | if !isMap { 63 | binder.Name = fieldName 64 | target = val.FieldByName(fieldName) 65 | } 66 | 67 | if isMap { 68 | tpe := binder.Type() 69 | if tpe == nil { 70 | if param.Schema.Type.Contains(typeArray) { 71 | tpe = reflect.TypeOf([]interface{}{}) 72 | } else { 73 | tpe = reflect.TypeOf(map[string]interface{}{}) 74 | } 75 | } 76 | target = reflect.Indirect(reflect.New(tpe)) 77 | } 78 | 79 | if !target.IsValid() { 80 | result = append(result, errors.New(http.StatusInternalServerError, "parameter name %q is an unknown field", binder.Name)) 81 | continue 82 | } 83 | 84 | if err := binder.Bind(request, routeParams, consumer, target); err != nil { 85 | result = append(result, err) 86 | continue 87 | } 88 | 89 | if binder.validator != nil { 90 | rr := binder.validator.Validate(target.Interface()) 91 | if rr != nil && rr.HasErrors() { 92 | result = append(result, rr.AsError()) 93 | } 94 | } 95 | 96 | if isMap { 97 | val.SetMapIndex(reflect.ValueOf(param.Name), target) 98 | } 99 | } 100 | 101 | if len(result) > 0 { 102 | return errors.CompositeValidationError(result...) 103 | } 104 | 105 | return nil 106 | } 107 | 108 | // SetLogger allows for injecting a logger to catch debug entries. 109 | // 110 | // The logger is enabled in DEBUG mode only. 111 | func (o *UntypedRequestBinder) SetLogger(lg logger.Logger) { 112 | o.debugLogf = debugLogfFunc(lg) 113 | } 114 | 115 | func (o *UntypedRequestBinder) setDebugLogf(fn func(string, ...any)) { 116 | o.debugLogf = fn 117 | } 118 | -------------------------------------------------------------------------------- /middleware/route_param_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestRouteParams(t *testing.T) { 24 | coll1 := RouteParams([]RouteParam{ 25 | {"blah", "foo"}, 26 | {"abc", "bar"}, 27 | {"ccc", "efg"}, 28 | }) 29 | 30 | v := coll1.Get("blah") 31 | assert.Equal(t, "foo", v) 32 | v2 := coll1.Get("abc") 33 | assert.Equal(t, "bar", v2) 34 | v3 := coll1.Get("ccc") 35 | assert.Equal(t, "efg", v3) 36 | v4 := coll1.Get("ydkdk") 37 | assert.Empty(t, v4) 38 | } 39 | -------------------------------------------------------------------------------- /middleware/security.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import "net/http" 18 | 19 | func newSecureAPI(ctx *Context, next http.Handler) http.Handler { 20 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 21 | route, rCtx, _ := ctx.RouteInfo(r) 22 | if rCtx != nil { 23 | r = rCtx 24 | } 25 | if route != nil && !route.NeedsAuth() { 26 | next.ServeHTTP(rw, r) 27 | return 28 | } 29 | 30 | _, rCtx, err := ctx.Authorize(r, route) 31 | if err != nil { 32 | ctx.Respond(rw, r, route.Produces, route, err) 33 | return 34 | } 35 | r = rCtx 36 | 37 | next.ServeHTTP(rw, r) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /middleware/security_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | stdcontext "context" 19 | "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | 26 | "github.com/go-openapi/runtime/internal/testing/petstore" 27 | ) 28 | 29 | func TestSecurityMiddleware(t *testing.T) { 30 | spec, api := petstore.NewAPI(t) 31 | context := NewContext(spec, api, nil) 32 | context.router = DefaultRouter(spec, context.api) 33 | mw := newSecureAPI(context, http.HandlerFunc(terminator)) 34 | 35 | t.Run("without auth", func(t *testing.T) { 36 | recorder := httptest.NewRecorder() 37 | request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil) 38 | require.NoError(t, err) 39 | 40 | mw.ServeHTTP(recorder, request) 41 | assert.Equal(t, http.StatusUnauthorized, recorder.Code) 42 | }) 43 | 44 | t.Run("with wrong password", func(t *testing.T) { 45 | recorder := httptest.NewRecorder() 46 | request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil) 47 | require.NoError(t, err) 48 | request.SetBasicAuth("admin", "wrong") 49 | 50 | mw.ServeHTTP(recorder, request) 51 | assert.Equal(t, http.StatusUnauthorized, recorder.Code) 52 | assert.NotEmpty(t, recorder.Header().Get("WWW-Authenticate")) 53 | }) 54 | 55 | t.Run("with correct password", func(t *testing.T) { 56 | recorder := httptest.NewRecorder() 57 | request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil) 58 | require.NoError(t, err) 59 | request.SetBasicAuth("admin", "admin") 60 | 61 | mw.ServeHTTP(recorder, request) 62 | assert.Equal(t, http.StatusOK, recorder.Code) 63 | }) 64 | 65 | t.Run("with unauthenticated path", func(t *testing.T) { 66 | recorder := httptest.NewRecorder() 67 | request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "//apipets/1", nil) 68 | require.NoError(t, err) 69 | 70 | mw.ServeHTTP(recorder, request) 71 | assert.Equal(t, http.StatusOK, recorder.Code) 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /middleware/spec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http" 19 | "path" 20 | ) 21 | 22 | const ( 23 | contentTypeHeader = "Content-Type" 24 | applicationJSON = "application/json" 25 | ) 26 | 27 | // SpecOption can be applied to the Spec serving middleware 28 | type SpecOption func(*specOptions) 29 | 30 | var defaultSpecOptions = specOptions{ 31 | Path: "", 32 | Document: "swagger.json", 33 | } 34 | 35 | type specOptions struct { 36 | Path string 37 | Document string 38 | } 39 | 40 | func specOptionsWithDefaults(opts []SpecOption) specOptions { 41 | o := defaultSpecOptions 42 | for _, apply := range opts { 43 | apply(&o) 44 | } 45 | 46 | return o 47 | } 48 | 49 | // Spec creates a middleware to serve a swagger spec as a JSON document. 50 | // 51 | // This allows for altering the spec before starting the http listener. 52 | // 53 | // The basePath argument indicates the path of the spec document (defaults to "/"). 54 | // Additional SpecOption can be used to change the name of the document (defaults to "swagger.json"). 55 | func Spec(basePath string, b []byte, next http.Handler, opts ...SpecOption) http.Handler { 56 | if basePath == "" { 57 | basePath = "/" 58 | } 59 | o := specOptionsWithDefaults(opts) 60 | pth := path.Join(basePath, o.Path, o.Document) 61 | 62 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 63 | if path.Clean(r.URL.Path) == pth { 64 | rw.Header().Set(contentTypeHeader, applicationJSON) 65 | rw.WriteHeader(http.StatusOK) 66 | _, _ = rw.Write(b) 67 | 68 | return 69 | } 70 | 71 | if next != nil { 72 | next.ServeHTTP(rw, r) 73 | 74 | return 75 | } 76 | 77 | rw.Header().Set(contentTypeHeader, applicationJSON) 78 | rw.WriteHeader(http.StatusNotFound) 79 | }) 80 | } 81 | 82 | // WithSpecPath sets the path to be joined to the base path of the Spec middleware. 83 | // 84 | // This is empty by default. 85 | func WithSpecPath(pth string) SpecOption { 86 | return func(o *specOptions) { 87 | o.Path = pth 88 | } 89 | } 90 | 91 | // WithSpecDocument sets the name of the JSON document served as a spec. 92 | // 93 | // By default, this is "swagger.json" 94 | func WithSpecDocument(doc string) SpecOption { 95 | return func(o *specOptions) { 96 | if doc == "" { 97 | return 98 | } 99 | 100 | o.Document = doc 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /middleware/spec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | 26 | "github.com/go-openapi/runtime" 27 | "github.com/go-openapi/runtime/internal/testing/petstore" 28 | ) 29 | 30 | func TestServeSpecMiddleware(t *testing.T) { 31 | spec, api := petstore.NewAPI(t) 32 | ctx := NewContext(spec, api, nil) 33 | 34 | t.Run("Spec handler", func(t *testing.T) { 35 | handler := Spec("", ctx.spec.Raw(), nil) 36 | 37 | t.Run("serves spec", func(t *testing.T) { 38 | request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/swagger.json", nil) 39 | require.NoError(t, err) 40 | request.Header.Add(runtime.HeaderContentType, runtime.JSONMime) 41 | recorder := httptest.NewRecorder() 42 | 43 | handler.ServeHTTP(recorder, request) 44 | assert.Equal(t, http.StatusOK, recorder.Code) 45 | 46 | responseHeaders := recorder.Result().Header 47 | responseContentType := responseHeaders.Get("Content-Type") 48 | assert.Equal(t, applicationJSON, responseContentType) //nolint:testifylint 49 | 50 | responseBody := recorder.Body 51 | require.NotNil(t, responseBody) 52 | require.JSONEq(t, string(spec.Raw()), responseBody.String()) 53 | }) 54 | 55 | t.Run("returns 404 when no next handler", func(t *testing.T) { 56 | request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/api/pets", nil) 57 | require.NoError(t, err) 58 | request.Header.Add(runtime.HeaderContentType, runtime.JSONMime) 59 | recorder := httptest.NewRecorder() 60 | 61 | handler.ServeHTTP(recorder, request) 62 | assert.Equal(t, http.StatusNotFound, recorder.Code) 63 | }) 64 | 65 | t.Run("forwards to next handler for other url", func(t *testing.T) { 66 | handler = Spec("", ctx.spec.Raw(), http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { 67 | rw.WriteHeader(http.StatusOK) 68 | })) 69 | request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/api/pets", nil) 70 | require.NoError(t, err) 71 | request.Header.Add(runtime.HeaderContentType, runtime.JSONMime) 72 | recorder := httptest.NewRecorder() 73 | 74 | handler.ServeHTTP(recorder, request) 75 | assert.Equal(t, http.StatusOK, recorder.Code) 76 | }) 77 | }) 78 | 79 | t.Run("Spec handler with options", func(t *testing.T) { 80 | handler := Spec("/swagger", ctx.spec.Raw(), nil, 81 | WithSpecPath("spec"), 82 | WithSpecDocument("myapi-swagger.json"), 83 | ) 84 | 85 | t.Run("serves spec", func(t *testing.T) { 86 | request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/swagger/spec/myapi-swagger.json", nil) 87 | require.NoError(t, err) 88 | request.Header.Add(runtime.HeaderContentType, runtime.JSONMime) 89 | recorder := httptest.NewRecorder() 90 | 91 | handler.ServeHTTP(recorder, request) 92 | assert.Equal(t, http.StatusOK, recorder.Code) 93 | }) 94 | 95 | t.Run("should not find spec there", func(t *testing.T) { 96 | request, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/swagger.json", nil) 97 | require.NoError(t, err) 98 | request.Header.Add(runtime.HeaderContentType, runtime.JSONMime) 99 | recorder := httptest.NewRecorder() 100 | 101 | handler.ServeHTTP(recorder, request) 102 | assert.Equal(t, http.StatusNotFound, recorder.Code) 103 | }) 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /middleware/swaggerui.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "html/template" 7 | "net/http" 8 | "path" 9 | ) 10 | 11 | // SwaggerUIOpts configures the SwaggerUI middleware 12 | type SwaggerUIOpts struct { 13 | // BasePath for the API, defaults to: / 14 | BasePath string 15 | 16 | // Path combines with BasePath to construct the path to the UI, defaults to: "docs". 17 | Path string 18 | 19 | // SpecURL is the URL of the spec document. 20 | // 21 | // Defaults to: /swagger.json 22 | SpecURL string 23 | 24 | // Title for the documentation site, default to: API documentation 25 | Title string 26 | 27 | // Template specifies a custom template to serve the UI 28 | Template string 29 | 30 | // OAuthCallbackURL the url called after OAuth2 login 31 | OAuthCallbackURL string 32 | 33 | // The three components needed to embed swagger-ui 34 | 35 | // SwaggerURL points to the js that generates the SwaggerUI site. 36 | // 37 | // Defaults to: https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js 38 | SwaggerURL string 39 | 40 | SwaggerPresetURL string 41 | SwaggerStylesURL string 42 | 43 | Favicon32 string 44 | Favicon16 string 45 | } 46 | 47 | // EnsureDefaults in case some options are missing 48 | func (r *SwaggerUIOpts) EnsureDefaults() { 49 | r.ensureDefaults() 50 | 51 | if r.Template == "" { 52 | r.Template = swaggeruiTemplate 53 | } 54 | } 55 | 56 | func (r *SwaggerUIOpts) EnsureDefaultsOauth2() { 57 | r.ensureDefaults() 58 | 59 | if r.Template == "" { 60 | r.Template = swaggerOAuthTemplate 61 | } 62 | } 63 | 64 | func (r *SwaggerUIOpts) ensureDefaults() { 65 | common := toCommonUIOptions(r) 66 | common.EnsureDefaults() 67 | fromCommonToAnyOptions(common, r) 68 | 69 | // swaggerui-specifics 70 | if r.OAuthCallbackURL == "" { 71 | r.OAuthCallbackURL = path.Join(r.BasePath, r.Path, "oauth2-callback") 72 | } 73 | if r.SwaggerURL == "" { 74 | r.SwaggerURL = swaggerLatest 75 | } 76 | if r.SwaggerPresetURL == "" { 77 | r.SwaggerPresetURL = swaggerPresetLatest 78 | } 79 | if r.SwaggerStylesURL == "" { 80 | r.SwaggerStylesURL = swaggerStylesLatest 81 | } 82 | if r.Favicon16 == "" { 83 | r.Favicon16 = swaggerFavicon16Latest 84 | } 85 | if r.Favicon32 == "" { 86 | r.Favicon32 = swaggerFavicon32Latest 87 | } 88 | } 89 | 90 | // SwaggerUI creates a middleware to serve a documentation site for a swagger spec. 91 | // 92 | // This allows for altering the spec before starting the http listener. 93 | func SwaggerUI(opts SwaggerUIOpts, next http.Handler) http.Handler { 94 | opts.EnsureDefaults() 95 | 96 | pth := path.Join(opts.BasePath, opts.Path) 97 | tmpl := template.Must(template.New("swaggerui").Parse(opts.Template)) 98 | assets := bytes.NewBuffer(nil) 99 | if err := tmpl.Execute(assets, opts); err != nil { 100 | panic(fmt.Errorf("cannot execute template: %w", err)) 101 | } 102 | 103 | return serveUI(pth, assets.Bytes(), next) 104 | } 105 | 106 | const ( 107 | swaggerLatest = "https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js" 108 | swaggerPresetLatest = "https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js" 109 | swaggerStylesLatest = "https://unpkg.com/swagger-ui-dist/swagger-ui.css" 110 | swaggerFavicon32Latest = "https://unpkg.com/swagger-ui-dist/favicon-32x32.png" 111 | swaggerFavicon16Latest = "https://unpkg.com/swagger-ui-dist/favicon-16x16.png" 112 | swaggeruiTemplate = ` 113 | 114 | 115 | 116 | 117 | {{ .Title }} 118 | 119 | 120 | 121 | 122 | 143 | 144 | 145 | 146 |
147 | 148 | 149 | 150 | 172 | 173 | 174 | ` 175 | ) 176 | -------------------------------------------------------------------------------- /middleware/swaggerui_oauth2.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "text/template" 8 | ) 9 | 10 | func SwaggerUIOAuth2Callback(opts SwaggerUIOpts, next http.Handler) http.Handler { 11 | opts.EnsureDefaultsOauth2() 12 | 13 | pth := opts.OAuthCallbackURL 14 | tmpl := template.Must(template.New("swaggeroauth").Parse(opts.Template)) 15 | assets := bytes.NewBuffer(nil) 16 | if err := tmpl.Execute(assets, opts); err != nil { 17 | panic(fmt.Errorf("cannot execute template: %w", err)) 18 | } 19 | 20 | return serveUI(pth, assets.Bytes(), next) 21 | } 22 | 23 | const ( 24 | swaggerOAuthTemplate = ` 25 | 26 | 27 | 28 | {{ .Title }} 29 | 30 | 31 | 102 | 103 | 104 | ` 105 | ) 106 | -------------------------------------------------------------------------------- /middleware/swaggerui_oauth2_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestSwaggerUIOAuth2CallbackMiddleware(t *testing.T) { 15 | t.Run("with defaults", func(t *testing.T) { 16 | doc := SwaggerUIOAuth2Callback(SwaggerUIOpts{}, nil) 17 | 18 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/docs/oauth2-callback", nil) 19 | require.NoError(t, err) 20 | recorder := httptest.NewRecorder() 21 | 22 | doc.ServeHTTP(recorder, req) 23 | require.Equal(t, http.StatusOK, recorder.Code) 24 | assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get(contentTypeHeader)) 25 | 26 | var o SwaggerUIOpts 27 | o.EnsureDefaultsOauth2() 28 | htmlResponse := recorder.Body.String() 29 | assert.Contains(t, htmlResponse, fmt.Sprintf("%s", o.Title)) 30 | assert.Contains(t, htmlResponse, `oauth2.auth.schema.get("flow") === "accessCode"`) 31 | }) 32 | 33 | t.Run("edge cases", func(t *testing.T) { 34 | t.Run("with custom template that fails to execute", func(t *testing.T) { 35 | assert.Panics(t, func() { 36 | SwaggerUIOAuth2Callback(SwaggerUIOpts{ 37 | Template: ` 38 | 39 | spec-url='{{ .Unknown }}' 40 | 41 | `, 42 | }, nil) 43 | }) 44 | }) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /middleware/swaggerui_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestSwaggerUIMiddleware(t *testing.T) { 16 | var o SwaggerUIOpts 17 | o.EnsureDefaults() 18 | swui := SwaggerUI(o, nil) 19 | 20 | t.Run("with defaults ", func(t *testing.T) { 21 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/docs", nil) 22 | require.NoError(t, err) 23 | recorder := httptest.NewRecorder() 24 | 25 | swui.ServeHTTP(recorder, req) 26 | assert.Equal(t, http.StatusOK, recorder.Code) 27 | 28 | assert.Equal(t, "text/html; charset=utf-8", recorder.Header().Get(contentTypeHeader)) 29 | assert.Contains(t, recorder.Body.String(), fmt.Sprintf("%s", o.Title)) 30 | assert.Contains(t, recorder.Body.String(), fmt.Sprintf(`url: '%s',`, strings.ReplaceAll(o.SpecURL, `/`, `\/`))) 31 | assert.Contains(t, recorder.Body.String(), swaggerLatest) 32 | assert.Contains(t, recorder.Body.String(), swaggerPresetLatest) 33 | assert.Contains(t, recorder.Body.String(), swaggerStylesLatest) 34 | assert.Contains(t, recorder.Body.String(), swaggerFavicon16Latest) 35 | assert.Contains(t, recorder.Body.String(), swaggerFavicon32Latest) 36 | }) 37 | 38 | t.Run("with path with a trailing / (issue #238)", func(t *testing.T) { 39 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/docs/", nil) 40 | require.NoError(t, err) 41 | recorder := httptest.NewRecorder() 42 | 43 | swui.ServeHTTP(recorder, req) 44 | assert.Equal(t, http.StatusOK, recorder.Code) 45 | }) 46 | 47 | t.Run("should yield not found", func(t *testing.T) { 48 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/nowhere", nil) 49 | require.NoError(t, err) 50 | recorder := httptest.NewRecorder() 51 | 52 | swui.ServeHTTP(recorder, req) 53 | assert.Equal(t, http.StatusNotFound, recorder.Code) 54 | }) 55 | 56 | t.Run("edge cases", func(t *testing.T) { 57 | t.Run("with custom template that fails to execute", func(t *testing.T) { 58 | assert.Panics(t, func() { 59 | SwaggerUI(SwaggerUIOpts{ 60 | Template: ` 61 | 62 | spec-url='{{ .Unknown }}' 63 | 64 | `, 65 | }, nil) 66 | }) 67 | }) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /middleware/ui_options.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | "net/http" 8 | "path" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | // constants that are common to all UI-serving middlewares 14 | defaultDocsPath = "docs" 15 | defaultDocsURL = "/swagger.json" 16 | defaultDocsTitle = "API Documentation" 17 | ) 18 | 19 | // uiOptions defines common options for UI serving middlewares. 20 | type uiOptions struct { 21 | // BasePath for the UI, defaults to: / 22 | BasePath string 23 | 24 | // Path combines with BasePath to construct the path to the UI, defaults to: "docs". 25 | Path string 26 | 27 | // SpecURL is the URL of the spec document. 28 | // 29 | // Defaults to: /swagger.json 30 | SpecURL string 31 | 32 | // Title for the documentation site, default to: API documentation 33 | Title string 34 | 35 | // Template specifies a custom template to serve the UI 36 | Template string 37 | } 38 | 39 | // toCommonUIOptions converts any UI option type to retain the common options. 40 | // 41 | // This uses gob encoding/decoding to convert common fields from one struct to another. 42 | func toCommonUIOptions(opts interface{}) uiOptions { 43 | var buf bytes.Buffer 44 | enc := gob.NewEncoder(&buf) 45 | dec := gob.NewDecoder(&buf) 46 | var o uiOptions 47 | err := enc.Encode(opts) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | err = dec.Decode(&o) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | return o 58 | } 59 | 60 | func fromCommonToAnyOptions[T any](source uiOptions, target *T) { 61 | var buf bytes.Buffer 62 | enc := gob.NewEncoder(&buf) 63 | dec := gob.NewDecoder(&buf) 64 | err := enc.Encode(source) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | err = dec.Decode(target) 70 | if err != nil { 71 | panic(err) 72 | } 73 | } 74 | 75 | // UIOption can be applied to UI serving middleware, such as Context.APIHandler or 76 | // Context.APIHandlerSwaggerUI to alter the defaut behavior. 77 | type UIOption func(*uiOptions) 78 | 79 | func uiOptionsWithDefaults(opts []UIOption) uiOptions { 80 | var o uiOptions 81 | for _, apply := range opts { 82 | apply(&o) 83 | } 84 | 85 | return o 86 | } 87 | 88 | // WithUIBasePath sets the base path from where to serve the UI assets. 89 | // 90 | // By default, Context middleware sets this value to the API base path. 91 | func WithUIBasePath(base string) UIOption { 92 | return func(o *uiOptions) { 93 | if !strings.HasPrefix(base, "/") { 94 | base = "/" + base 95 | } 96 | o.BasePath = base 97 | } 98 | } 99 | 100 | // WithUIPath sets the path from where to serve the UI assets (i.e. /{basepath}/{path}. 101 | func WithUIPath(pth string) UIOption { 102 | return func(o *uiOptions) { 103 | o.Path = pth 104 | } 105 | } 106 | 107 | // WithUISpecURL sets the path from where to serve swagger spec document. 108 | // 109 | // This may be specified as a full URL or a path. 110 | // 111 | // By default, this is "/swagger.json" 112 | func WithUISpecURL(specURL string) UIOption { 113 | return func(o *uiOptions) { 114 | o.SpecURL = specURL 115 | } 116 | } 117 | 118 | // WithUITitle sets the title of the UI. 119 | // 120 | // By default, Context middleware sets this value to the title found in the API spec. 121 | func WithUITitle(title string) UIOption { 122 | return func(o *uiOptions) { 123 | o.Title = title 124 | } 125 | } 126 | 127 | // WithTemplate allows to set a custom template for the UI. 128 | // 129 | // UI middleware will panic if the template does not parse or execute properly. 130 | func WithTemplate(tpl string) UIOption { 131 | return func(o *uiOptions) { 132 | o.Template = tpl 133 | } 134 | } 135 | 136 | // EnsureDefaults in case some options are missing 137 | func (r *uiOptions) EnsureDefaults() { 138 | if r.BasePath == "" { 139 | r.BasePath = "/" 140 | } 141 | if r.Path == "" { 142 | r.Path = defaultDocsPath 143 | } 144 | if r.SpecURL == "" { 145 | r.SpecURL = defaultDocsURL 146 | } 147 | if r.Title == "" { 148 | r.Title = defaultDocsTitle 149 | } 150 | } 151 | 152 | // serveUI creates a middleware that serves a templated asset as text/html. 153 | func serveUI(pth string, assets []byte, next http.Handler) http.Handler { 154 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 155 | if path.Clean(r.URL.Path) == pth { 156 | rw.Header().Set(contentTypeHeader, "text/html; charset=utf-8") 157 | rw.WriteHeader(http.StatusOK) 158 | _, _ = rw.Write(assets) 159 | 160 | return 161 | } 162 | 163 | if next != nil { 164 | next.ServeHTTP(rw, r) 165 | 166 | return 167 | } 168 | 169 | rw.Header().Set(contentTypeHeader, "text/plain") 170 | rw.WriteHeader(http.StatusNotFound) 171 | _, _ = rw.Write([]byte(fmt.Sprintf("%q not found", pth))) 172 | }) 173 | } 174 | -------------------------------------------------------------------------------- /middleware/ui_options_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestConvertOptions(t *testing.T) { 10 | t.Run("from any UI options to uiOptions", func(t *testing.T) { 11 | t.Run("from RedocOpts", func(t *testing.T) { 12 | in := RedocOpts{ 13 | BasePath: "a", 14 | Path: "b", 15 | SpecURL: "c", 16 | Template: "d", 17 | Title: "e", 18 | RedocURL: "f", 19 | } 20 | out := toCommonUIOptions(in) 21 | 22 | require.Equal(t, "a", out.BasePath) 23 | require.Equal(t, "b", out.Path) 24 | require.Equal(t, "c", out.SpecURL) 25 | require.Equal(t, "d", out.Template) 26 | require.Equal(t, "e", out.Title) 27 | }) 28 | 29 | t.Run("from RapiDocOpts", func(t *testing.T) { 30 | in := RapiDocOpts{ 31 | BasePath: "a", 32 | Path: "b", 33 | SpecURL: "c", 34 | Template: "d", 35 | Title: "e", 36 | RapiDocURL: "f", 37 | } 38 | out := toCommonUIOptions(in) 39 | 40 | require.Equal(t, "a", out.BasePath) 41 | require.Equal(t, "b", out.Path) 42 | require.Equal(t, "c", out.SpecURL) 43 | require.Equal(t, "d", out.Template) 44 | require.Equal(t, "e", out.Title) 45 | }) 46 | 47 | t.Run("from SwaggerUIOpts", func(t *testing.T) { 48 | in := SwaggerUIOpts{ 49 | BasePath: "a", 50 | Path: "b", 51 | SpecURL: "c", 52 | Template: "d", 53 | Title: "e", 54 | SwaggerURL: "f", 55 | } 56 | out := toCommonUIOptions(in) 57 | 58 | require.Equal(t, "a", out.BasePath) 59 | require.Equal(t, "b", out.Path) 60 | require.Equal(t, "c", out.SpecURL) 61 | require.Equal(t, "d", out.Template) 62 | require.Equal(t, "e", out.Title) 63 | }) 64 | }) 65 | 66 | t.Run("from uiOptions to any UI options", func(t *testing.T) { 67 | in := uiOptions{ 68 | BasePath: "a", 69 | Path: "b", 70 | SpecURL: "c", 71 | Template: "d", 72 | Title: "e", 73 | } 74 | 75 | t.Run("to RedocOpts", func(t *testing.T) { 76 | var out RedocOpts 77 | fromCommonToAnyOptions(in, &out) 78 | require.Equal(t, "a", out.BasePath) 79 | require.Equal(t, "b", out.Path) 80 | require.Equal(t, "c", out.SpecURL) 81 | require.Equal(t, "d", out.Template) 82 | require.Equal(t, "e", out.Title) 83 | }) 84 | 85 | t.Run("to RapiDocOpts", func(t *testing.T) { 86 | var out RapiDocOpts 87 | fromCommonToAnyOptions(in, &out) 88 | require.Equal(t, "a", out.BasePath) 89 | require.Equal(t, "b", out.Path) 90 | require.Equal(t, "c", out.SpecURL) 91 | require.Equal(t, "d", out.Template) 92 | require.Equal(t, "e", out.Title) 93 | }) 94 | 95 | t.Run("to SwaggerUIOpts", func(t *testing.T) { 96 | var out SwaggerUIOpts 97 | fromCommonToAnyOptions(in, &out) 98 | require.Equal(t, "a", out.BasePath) 99 | require.Equal(t, "b", out.Path) 100 | require.Equal(t, "c", out.SpecURL) 101 | require.Equal(t, "d", out.Template) 102 | require.Equal(t, "e", out.Title) 103 | }) 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /middleware/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "mime" 19 | "net/http" 20 | "strings" 21 | 22 | "github.com/go-openapi/errors" 23 | "github.com/go-openapi/swag" 24 | 25 | "github.com/go-openapi/runtime" 26 | ) 27 | 28 | type validation struct { 29 | context *Context 30 | result []error 31 | request *http.Request 32 | route *MatchedRoute 33 | bound map[string]interface{} 34 | } 35 | 36 | // ContentType validates the content type of a request 37 | func validateContentType(allowed []string, actual string) error { 38 | if len(allowed) == 0 { 39 | return nil 40 | } 41 | mt, _, err := mime.ParseMediaType(actual) 42 | if err != nil { 43 | return errors.InvalidContentType(actual, allowed) 44 | } 45 | if swag.ContainsStringsCI(allowed, mt) { 46 | return nil 47 | } 48 | if swag.ContainsStringsCI(allowed, "*/*") { 49 | return nil 50 | } 51 | parts := strings.Split(actual, "/") 52 | if len(parts) == 2 && swag.ContainsStringsCI(allowed, parts[0]+"/*") { 53 | return nil 54 | } 55 | return errors.InvalidContentType(actual, allowed) 56 | } 57 | 58 | func validateRequest(ctx *Context, request *http.Request, route *MatchedRoute) *validation { 59 | validate := &validation{ 60 | context: ctx, 61 | request: request, 62 | route: route, 63 | bound: make(map[string]interface{}), 64 | } 65 | validate.debugLogf("validating request %s %s", request.Method, request.URL.EscapedPath()) 66 | 67 | validate.contentType() 68 | if len(validate.result) == 0 { 69 | validate.responseFormat() 70 | } 71 | if len(validate.result) == 0 { 72 | validate.parameters() 73 | } 74 | 75 | return validate 76 | } 77 | 78 | func (v *validation) debugLogf(format string, args ...any) { 79 | v.context.debugLogf(format, args...) 80 | } 81 | 82 | func (v *validation) parameters() { 83 | v.debugLogf("validating request parameters for %s %s", v.request.Method, v.request.URL.EscapedPath()) 84 | if result := v.route.Binder.Bind(v.request, v.route.Params, v.route.Consumer, v.bound); result != nil { 85 | if result.Error() == "validation failure list" { 86 | for _, e := range result.(*errors.Validation).Value.([]interface{}) { 87 | v.result = append(v.result, e.(error)) 88 | } 89 | return 90 | } 91 | v.result = append(v.result, result) 92 | } 93 | } 94 | 95 | func (v *validation) contentType() { 96 | if len(v.result) == 0 && runtime.HasBody(v.request) { 97 | v.debugLogf("validating body content type for %s %s", v.request.Method, v.request.URL.EscapedPath()) 98 | ct, _, req, err := v.context.ContentType(v.request) 99 | if err != nil { 100 | v.result = append(v.result, err) 101 | } else { 102 | v.request = req 103 | } 104 | 105 | if len(v.result) == 0 { 106 | v.debugLogf("validating content type for %q against [%s]", ct, strings.Join(v.route.Consumes, ", ")) 107 | if err := validateContentType(v.route.Consumes, ct); err != nil { 108 | v.result = append(v.result, err) 109 | } 110 | } 111 | if ct != "" && v.route.Consumer == nil { 112 | cons, ok := v.route.Consumers[ct] 113 | if !ok { 114 | v.result = append(v.result, errors.New(http.StatusInternalServerError, "no consumer registered for %s", ct)) 115 | } else { 116 | v.route.Consumer = cons 117 | } 118 | } 119 | } 120 | } 121 | 122 | func (v *validation) responseFormat() { 123 | // if the route provides values for Produces and no format could be identify then return an error. 124 | // if the route does not specify values for Produces then treat request as valid since the API designer 125 | // choose not to specify the format for responses. 126 | if str, rCtx := v.context.ResponseFormat(v.request, v.route.Produces); str == "" && len(v.route.Produces) > 0 { 127 | v.request = rCtx 128 | v.result = append(v.result, errors.InvalidResponseFormat(v.request.Header.Get(runtime.HeaderAccept), v.route.Produces)) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "bufio" 19 | "context" 20 | "errors" 21 | "io" 22 | "net/http" 23 | "strings" 24 | 25 | "github.com/go-openapi/swag" 26 | ) 27 | 28 | // CanHaveBody returns true if this method can have a body 29 | func CanHaveBody(method string) bool { 30 | mn := strings.ToUpper(method) 31 | return mn == "POST" || mn == "PUT" || mn == "PATCH" || mn == "DELETE" 32 | } 33 | 34 | // IsSafe returns true if this is a request with a safe method 35 | func IsSafe(r *http.Request) bool { 36 | mn := strings.ToUpper(r.Method) 37 | return mn == "GET" || mn == "HEAD" 38 | } 39 | 40 | // AllowsBody returns true if the request allows for a body 41 | func AllowsBody(r *http.Request) bool { 42 | mn := strings.ToUpper(r.Method) 43 | return mn != "HEAD" 44 | } 45 | 46 | // HasBody returns true if this method needs a content-type 47 | func HasBody(r *http.Request) bool { 48 | // happy case: we have a content length set 49 | if r.ContentLength > 0 { 50 | return true 51 | } 52 | 53 | if r.Header.Get("Content-Length") != "" { 54 | // in this case, no Transfer-Encoding should be present 55 | // we have a header set but it was explicitly set to 0, so we assume no body 56 | return false 57 | } 58 | 59 | rdr := newPeekingReader(r.Body) 60 | r.Body = rdr 61 | return rdr.HasContent() 62 | } 63 | 64 | func newPeekingReader(r io.ReadCloser) *peekingReader { 65 | if r == nil { 66 | return nil 67 | } 68 | return &peekingReader{ 69 | underlying: bufio.NewReader(r), 70 | orig: r, 71 | } 72 | } 73 | 74 | type peekingReader struct { 75 | underlying interface { 76 | Buffered() int 77 | Peek(int) ([]byte, error) 78 | Read([]byte) (int, error) 79 | } 80 | orig io.ReadCloser 81 | } 82 | 83 | func (p *peekingReader) HasContent() bool { 84 | if p == nil { 85 | return false 86 | } 87 | if p.underlying.Buffered() > 0 { 88 | return true 89 | } 90 | b, err := p.underlying.Peek(1) 91 | if err != nil { 92 | return false 93 | } 94 | return len(b) > 0 95 | } 96 | 97 | func (p *peekingReader) Read(d []byte) (int, error) { 98 | if p == nil { 99 | return 0, io.EOF 100 | } 101 | if p.underlying == nil { 102 | return 0, io.ErrUnexpectedEOF 103 | } 104 | return p.underlying.Read(d) 105 | } 106 | 107 | func (p *peekingReader) Close() error { 108 | if p.underlying == nil { 109 | return errors.New("reader already closed") 110 | } 111 | p.underlying = nil 112 | if p.orig != nil { 113 | return p.orig.Close() 114 | } 115 | return nil 116 | } 117 | 118 | // JSONRequest creates a new http request with json headers set. 119 | // 120 | // It uses context.Background. 121 | func JSONRequest(method, urlStr string, body io.Reader) (*http.Request, error) { 122 | req, err := http.NewRequestWithContext(context.Background(), method, urlStr, body) 123 | if err != nil { 124 | return nil, err 125 | } 126 | req.Header.Add(HeaderContentType, JSONMime) 127 | req.Header.Add(HeaderAccept, JSONMime) 128 | return req, nil 129 | } 130 | 131 | // Gettable for things with a method GetOK(string) (data string, hasKey bool, hasValue bool) 132 | type Gettable interface { 133 | GetOK(string) ([]string, bool, bool) 134 | } 135 | 136 | // ReadSingleValue reads a single value from the source 137 | func ReadSingleValue(values Gettable, name string) string { 138 | vv, _, hv := values.GetOK(name) 139 | if hv { 140 | return vv[len(vv)-1] 141 | } 142 | return "" 143 | } 144 | 145 | // ReadCollectionValue reads a collection value from a string data source 146 | func ReadCollectionValue(values Gettable, name, collectionFormat string) []string { 147 | v := ReadSingleValue(values, name) 148 | return swag.SplitByFormat(v, collectionFormat) 149 | } 150 | -------------------------------------------------------------------------------- /security/authorizer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package security 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/go-openapi/runtime" 21 | ) 22 | 23 | // Authorized provides a default implementation of the Authorizer interface where all 24 | // requests are authorized (successful) 25 | func Authorized() runtime.Authorizer { 26 | return runtime.AuthorizerFunc(func(_ *http.Request, _ interface{}) error { return nil }) 27 | } 28 | -------------------------------------------------------------------------------- /security/authorizer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package security 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestAuthorized(t *testing.T) { 27 | authorizer := Authorized() 28 | 29 | err := authorizer.Authorize(nil, nil) 30 | require.NoError(t, err) 31 | } 32 | 33 | func TestAuthenticator(t *testing.T) { 34 | r, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) 35 | require.NoError(t, err) 36 | 37 | t.Run("with HttpAuthenticator", func(t *testing.T) { 38 | auth := HttpAuthenticator(func(_ *http.Request) (bool, interface{}, error) { return true, "test", nil }) 39 | 40 | t.Run("authenticator should work on *http.Request", func(t *testing.T) { 41 | isAuth, user, err := auth.Authenticate(r) 42 | require.NoError(t, err) 43 | assert.True(t, isAuth) 44 | assert.Equal(t, "test", user) 45 | }) 46 | 47 | t.Run("authenticator should work on *ScopedAuthRequest", func(t *testing.T) { 48 | scoped := &ScopedAuthRequest{Request: r} 49 | 50 | isAuth, user, err := auth.Authenticate(scoped) 51 | require.NoError(t, err) 52 | assert.True(t, isAuth) 53 | assert.Equal(t, "test", user) 54 | }) 55 | 56 | t.Run("authenticator should return false on other inputs", func(t *testing.T) { 57 | isAuth, user, err := auth.Authenticate("") 58 | require.NoError(t, err) 59 | assert.False(t, isAuth) 60 | assert.Empty(t, user) 61 | }) 62 | }) 63 | 64 | t.Run("with ScopedAuthenticator", func(t *testing.T) { 65 | auth := ScopedAuthenticator(func(_ *ScopedAuthRequest) (bool, interface{}, error) { return true, "test", nil }) 66 | 67 | t.Run("authenticator should work on *ScopedAuthRequest", func(t *testing.T) { 68 | scoped := &ScopedAuthRequest{Request: r} 69 | 70 | isAuth, user, err := auth.Authenticate(scoped) 71 | require.NoError(t, err) 72 | assert.True(t, isAuth) 73 | assert.Equal(t, "test", user) 74 | }) 75 | 76 | t.Run("authenticator should return false on other inputs", func(t *testing.T) { 77 | isAuth, user, err := auth.Authenticate("") 78 | require.NoError(t, err) 79 | assert.False(t, isAuth) 80 | assert.Empty(t, user) 81 | }) 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /statuses.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | // Statuses lists the most common HTTP status codes to default message 18 | // taken from https://httpstatuses.com/ 19 | var Statuses = map[int]string{ 20 | 100: "Continue", 21 | 101: "Switching Protocols", 22 | 102: "Processing", 23 | 103: "Checkpoint", 24 | 122: "URI too long", 25 | 200: "OK", 26 | 201: "Created", 27 | 202: "Accepted", 28 | 203: "Request Processed", 29 | 204: "No Content", 30 | 205: "Reset Content", 31 | 206: "Partial Content", 32 | 207: "Multi-Status", 33 | 208: "Already Reported", 34 | 226: "IM Used", 35 | 300: "Multiple Choices", 36 | 301: "Moved Permanently", 37 | 302: "Found", 38 | 303: "See Other", 39 | 304: "Not Modified", 40 | 305: "Use Proxy", 41 | 306: "Switch Proxy", 42 | 307: "Temporary Redirect", 43 | 308: "Permanent Redirect", 44 | 400: "Bad Request", 45 | 401: "Unauthorized", 46 | 402: "Payment Required", 47 | 403: "Forbidden", 48 | 404: "Not Found", 49 | 405: "Method Not Allowed", 50 | 406: "Not Acceptable", 51 | 407: "Proxy Authentication Required", 52 | 408: "Request Timeout", 53 | 409: "Conflict", 54 | 410: "Gone", 55 | 411: "Length Required", 56 | 412: "Precondition Failed", 57 | 413: "Request Entity Too Large", 58 | 414: "Request-URI Too Long", 59 | 415: "Unsupported Media Type", 60 | 416: "Request Range Not Satisfiable", 61 | 417: "Expectation Failed", 62 | 418: "I'm a teapot", 63 | 420: "Enhance Your Calm", 64 | 422: "Unprocessable Entity", 65 | 423: "Locked", 66 | 424: "Failed Dependency", 67 | 426: "Upgrade Required", 68 | 428: "Precondition Required", 69 | 429: "Too Many Requests", 70 | 431: "Request Header Fields Too Large", 71 | 444: "No Response", 72 | 449: "Retry With", 73 | 450: "Blocked by Windows Parental Controls", 74 | 451: "Wrong Exchange Server", 75 | 499: "Client Closed Request", 76 | 500: "Internal Server Error", 77 | 501: "Not Implemented", 78 | 502: "Bad Gateway", 79 | 503: "Service Unavailable", 80 | 504: "Gateway Timeout", 81 | 505: "HTTP Version Not Supported", 82 | 506: "Variant Also Negotiates", 83 | 507: "Insufficient Storage", 84 | 508: "Loop Detected", 85 | 509: "Bandwidth Limit Exceeded", 86 | 510: "Not Extended", 87 | 511: "Network Authentication Required", 88 | 598: "Network read timeout error", 89 | 599: "Network connect timeout error", 90 | } 91 | -------------------------------------------------------------------------------- /text.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "bytes" 19 | "encoding" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "reflect" 24 | 25 | "github.com/go-openapi/swag" 26 | ) 27 | 28 | // TextConsumer creates a new text consumer 29 | func TextConsumer() Consumer { 30 | return ConsumerFunc(func(reader io.Reader, data interface{}) error { 31 | if reader == nil { 32 | return errors.New("TextConsumer requires a reader") // early exit 33 | } 34 | 35 | buf := new(bytes.Buffer) 36 | _, err := buf.ReadFrom(reader) 37 | if err != nil { 38 | return err 39 | } 40 | b := buf.Bytes() 41 | 42 | // If the buffer is empty, no need to unmarshal it, which causes a panic. 43 | if len(b) == 0 { 44 | return nil 45 | } 46 | 47 | if tu, ok := data.(encoding.TextUnmarshaler); ok { 48 | err := tu.UnmarshalText(b) 49 | if err != nil { 50 | return fmt.Errorf("text consumer: %v", err) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | t := reflect.TypeOf(data) 57 | if data != nil && t.Kind() == reflect.Ptr { 58 | v := reflect.Indirect(reflect.ValueOf(data)) 59 | if t.Elem().Kind() == reflect.String { 60 | v.SetString(string(b)) 61 | return nil 62 | } 63 | } 64 | 65 | return fmt.Errorf("%v (%T) is not supported by the TextConsumer, %s", 66 | data, data, "can be resolved by supporting TextUnmarshaler interface") 67 | }) 68 | } 69 | 70 | // TextProducer creates a new text producer 71 | func TextProducer() Producer { 72 | return ProducerFunc(func(writer io.Writer, data interface{}) error { 73 | if writer == nil { 74 | return errors.New("TextProducer requires a writer") // early exit 75 | } 76 | 77 | if data == nil { 78 | return errors.New("no data given to produce text from") 79 | } 80 | 81 | if tm, ok := data.(encoding.TextMarshaler); ok { 82 | txt, err := tm.MarshalText() 83 | if err != nil { 84 | return fmt.Errorf("text producer: %v", err) 85 | } 86 | _, err = writer.Write(txt) 87 | return err 88 | } 89 | 90 | if str, ok := data.(error); ok { 91 | _, err := writer.Write([]byte(str.Error())) 92 | return err 93 | } 94 | 95 | if str, ok := data.(fmt.Stringer); ok { 96 | _, err := writer.Write([]byte(str.String())) 97 | return err 98 | } 99 | 100 | v := reflect.Indirect(reflect.ValueOf(data)) 101 | if t := v.Type(); t.Kind() == reflect.Struct || t.Kind() == reflect.Slice { 102 | b, err := swag.WriteJSON(data) 103 | if err != nil { 104 | return err 105 | } 106 | _, err = writer.Write(b) 107 | return err 108 | } 109 | if v.Kind() != reflect.String { 110 | return fmt.Errorf("%T is not a supported type by the TextProducer", data) 111 | } 112 | 113 | _, err := writer.Write([]byte(v.String())) 114 | return err 115 | }) 116 | } 117 | -------------------------------------------------------------------------------- /values.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | // Values typically represent parameters on a http request. 4 | type Values map[string][]string 5 | 6 | // GetOK returns the values collection for the given key. 7 | // When the key is present in the map it will return true for hasKey. 8 | // When the value is not empty it will return true for hasValue. 9 | func (v Values) GetOK(key string) (value []string, hasKey bool, hasValue bool) { 10 | value, hasKey = v[key] 11 | if !hasKey { 12 | return 13 | } 14 | if len(value) == 0 { 15 | return 16 | } 17 | hasValue = true 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /values_test.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestGetOK(t *testing.T) { 10 | m := make(map[string][]string) 11 | m["key1"] = []string{"value1"} 12 | m["key2"] = []string{} 13 | values := Values(m) 14 | 15 | v, hasKey, hasValue := values.GetOK("key1") 16 | require.Equal(t, []string{"value1"}, v) 17 | require.True(t, hasKey) 18 | require.True(t, hasValue) 19 | 20 | v, hasKey, hasValue = values.GetOK("key2") 21 | require.Equal(t, []string{}, v) 22 | require.True(t, hasKey) 23 | require.False(t, hasValue) 24 | 25 | v, hasKey, hasValue = values.GetOK("key3") 26 | require.Nil(t, v) 27 | require.False(t, hasKey) 28 | require.False(t, hasValue) 29 | } 30 | -------------------------------------------------------------------------------- /xml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "encoding/xml" 19 | "io" 20 | ) 21 | 22 | // XMLConsumer creates a new XML consumer 23 | func XMLConsumer() Consumer { 24 | return ConsumerFunc(func(reader io.Reader, data interface{}) error { 25 | dec := xml.NewDecoder(reader) 26 | return dec.Decode(data) 27 | }) 28 | } 29 | 30 | // XMLProducer creates a new XML producer 31 | func XMLProducer() Producer { 32 | return ProducerFunc(func(writer io.Writer, data interface{}) error { 33 | enc := xml.NewEncoder(writer) 34 | return enc.Encode(data) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /xml_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtime 16 | 17 | import ( 18 | "bytes" 19 | "encoding/xml" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | var consProdXML = `Somebody1` 28 | 29 | func TestXMLConsumer(t *testing.T) { 30 | cons := XMLConsumer() 31 | var data struct { 32 | XMLName xml.Name `xml:"person"` 33 | Name string `xml:"name"` 34 | ID int `xml:"id"` 35 | } 36 | err := cons.Consume(bytes.NewBufferString(consProdXML), &data) 37 | require.NoError(t, err) 38 | assert.Equal(t, "Somebody", data.Name) 39 | assert.Equal(t, 1, data.ID) 40 | } 41 | 42 | func TestXMLProducer(t *testing.T) { 43 | prod := XMLProducer() 44 | data := struct { 45 | XMLName xml.Name `xml:"person"` 46 | Name string `xml:"name"` 47 | ID int `xml:"id"` 48 | }{Name: "Somebody", ID: 1} 49 | 50 | rw := httptest.NewRecorder() 51 | err := prod.Produce(rw, data) 52 | require.NoError(t, err) 53 | assert.Equal(t, consProdXML, rw.Body.String()) 54 | } 55 | -------------------------------------------------------------------------------- /yamlpc/yaml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package yamlpc 16 | 17 | import ( 18 | "io" 19 | 20 | "github.com/go-openapi/runtime" 21 | "gopkg.in/yaml.v3" 22 | ) 23 | 24 | // YAMLConsumer creates a consumer for yaml data 25 | func YAMLConsumer() runtime.Consumer { 26 | return runtime.ConsumerFunc(func(r io.Reader, v interface{}) error { 27 | dec := yaml.NewDecoder(r) 28 | return dec.Decode(v) 29 | }) 30 | } 31 | 32 | // YAMLProducer creates a producer for yaml data 33 | func YAMLProducer() runtime.Producer { 34 | return runtime.ProducerFunc(func(w io.Writer, v interface{}) error { 35 | enc := yaml.NewEncoder(w) 36 | defer enc.Close() 37 | return enc.Encode(v) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /yamlpc/yaml_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 go-swagger maintainers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package yamlpc 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | var consProdYAML = "name: Somebody\nid: 1\n" 28 | 29 | func TestYAMLConsumer(t *testing.T) { 30 | cons := YAMLConsumer() 31 | var data struct { 32 | Name string 33 | ID int 34 | } 35 | err := cons.Consume(bytes.NewBufferString(consProdYAML), &data) 36 | require.NoError(t, err) 37 | assert.Equal(t, "Somebody", data.Name) 38 | assert.Equal(t, 1, data.ID) 39 | } 40 | 41 | func TestYAMLProducer(t *testing.T) { 42 | prod := YAMLProducer() 43 | data := struct { 44 | Name string `yaml:"name"` 45 | ID int `yaml:"id"` 46 | }{Name: "Somebody", ID: 1} 47 | 48 | rw := httptest.NewRecorder() 49 | err := prod.Produce(rw, data) 50 | require.NoError(t, err) 51 | assert.YAMLEq(t, consProdYAML, rw.Body.String()) 52 | } 53 | 54 | type failReaderWriter struct { 55 | } 56 | 57 | func (f *failReaderWriter) Read(_ []byte) (n int, err error) { 58 | return 0, errors.New("expected") 59 | } 60 | 61 | func (f *failReaderWriter) Write(_ []byte) (n int, err error) { 62 | return 0, errors.New("expected") 63 | } 64 | 65 | func TestFailYAMLWriter(t *testing.T) { 66 | prod := YAMLProducer() 67 | require.Error(t, prod.Produce(&failReaderWriter{}, nil)) 68 | } 69 | 70 | func TestFailYAMLReader(t *testing.T) { 71 | cons := YAMLConsumer() 72 | require.Error(t, cons.Consume(&failReaderWriter{}, nil)) 73 | } 74 | 75 | func TestYAMLConsumerObject(t *testing.T) { 76 | const yamlDoc = ` 77 | --- 78 | name: fred 79 | id: 123 80 | attributes: 81 | height: 12.3 82 | weight: 45 83 | list: 84 | - a 85 | - b 86 | ` 87 | cons := YAMLConsumer() 88 | var data struct { 89 | Name string 90 | ID int 91 | Attributes struct { 92 | Height float64 93 | Weight uint64 94 | List []string 95 | } 96 | } 97 | require.NoError(t, 98 | cons.Consume(bytes.NewBufferString(yamlDoc), &data), 99 | ) 100 | 101 | assert.Equal(t, "fred", data.Name) 102 | assert.Equal(t, 123, data.ID) 103 | assert.InDelta(t, 12.3, data.Attributes.Height, 1e-9) 104 | assert.Equal(t, uint64(45), data.Attributes.Weight) 105 | assert.Len(t, data.Attributes.List, 2) 106 | assert.Equal(t, "a", data.Attributes.List[0]) 107 | } 108 | --------------------------------------------------------------------------------