├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── dependabot.yaml └── workflows │ ├── auto-merge.yml │ └── go-test.yml ├── .gitignore ├── .golangci.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── api.go ├── api_test.go ├── auth.go ├── auth_test.go ├── doc.go ├── go.mod ├── go.sum ├── headers.go ├── middleware.go ├── middleware_test.go ├── parsing.go ├── parsing_test.go ├── schema.go └── schema_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf -------------------------------------------------------------------------------- /.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' ] 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 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: all 4 | disable: 5 | - cyclop 6 | - depguard 7 | - errchkjson 8 | - errorlint 9 | - exhaustruct 10 | - forcetypeassert 11 | - funlen 12 | - gochecknoglobals 13 | - gochecknoinits 14 | - gocognit 15 | - godot 16 | - godox 17 | - gosmopolitan 18 | - inamedparam 19 | - ireturn 20 | - lll 21 | - musttag 22 | - nestif 23 | - nlreturn 24 | - nonamedreturns 25 | - paralleltest 26 | - testpackage 27 | - thelper 28 | - tparallel 29 | - unparam 30 | - varnamelen 31 | - whitespace 32 | - wrapcheck 33 | - wsl 34 | settings: 35 | dupl: 36 | threshold: 200 37 | goconst: 38 | min-len: 2 39 | min-occurrences: 3 40 | gocyclo: 41 | min-complexity: 45 42 | exclusions: 43 | generated: lax 44 | presets: 45 | - comments 46 | - common-false-positives 47 | - legacy 48 | - std-error-handling 49 | paths: 50 | - third_party$ 51 | - builtin$ 52 | - examples$ 53 | formatters: 54 | enable: 55 | - gofmt 56 | - goimports 57 | exclusions: 58 | generated: lax 59 | paths: 60 | - third_party$ 61 | - builtin$ 62 | - examples$ 63 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI errors [![Build Status](https://github.com/go-openapi/errors/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/errors/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/errors/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/errors) 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/errors/master/LICENSE) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/errors.svg)](https://pkg.go.dev/github.com/go-openapi/errors) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/errors)](https://goreportcard.com/report/github.com/go-openapi/errors) 7 | 8 | Shared errors and error interface used throughout the various libraries found in the go-openapi toolkit. 9 | -------------------------------------------------------------------------------- /api.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 errors 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "net/http" 21 | "reflect" 22 | "strings" 23 | ) 24 | 25 | // DefaultHTTPCode is used when the error Code cannot be used as an HTTP code. 26 | var DefaultHTTPCode = http.StatusUnprocessableEntity 27 | 28 | // Error represents a error interface all swagger framework errors implement 29 | type Error interface { 30 | error 31 | Code() int32 32 | } 33 | 34 | type apiError struct { 35 | code int32 36 | message string 37 | } 38 | 39 | func (a *apiError) Error() string { 40 | return a.message 41 | } 42 | 43 | func (a *apiError) Code() int32 { 44 | return a.code 45 | } 46 | 47 | // MarshalJSON implements the JSON encoding interface 48 | func (a apiError) MarshalJSON() ([]byte, error) { 49 | return json.Marshal(map[string]interface{}{ 50 | "code": a.code, 51 | "message": a.message, 52 | }) 53 | } 54 | 55 | // New creates a new API error with a code and a message 56 | func New(code int32, message string, args ...interface{}) Error { 57 | if len(args) > 0 { 58 | return &apiError{ 59 | code: code, 60 | message: fmt.Sprintf(message, args...), 61 | } 62 | } 63 | return &apiError{ 64 | code: code, 65 | message: message, 66 | } 67 | } 68 | 69 | // NotFound creates a new not found error 70 | func NotFound(message string, args ...interface{}) Error { 71 | if message == "" { 72 | message = "Not found" 73 | } 74 | return New(http.StatusNotFound, fmt.Sprintf(message, args...)) 75 | } 76 | 77 | // NotImplemented creates a new not implemented error 78 | func NotImplemented(message string) Error { 79 | return New(http.StatusNotImplemented, message) 80 | } 81 | 82 | // MethodNotAllowedError represents an error for when the path matches but the method doesn't 83 | type MethodNotAllowedError struct { 84 | code int32 85 | Allowed []string 86 | message string 87 | } 88 | 89 | func (m *MethodNotAllowedError) Error() string { 90 | return m.message 91 | } 92 | 93 | // Code the error code 94 | func (m *MethodNotAllowedError) Code() int32 { 95 | return m.code 96 | } 97 | 98 | // MarshalJSON implements the JSON encoding interface 99 | func (m MethodNotAllowedError) MarshalJSON() ([]byte, error) { 100 | return json.Marshal(map[string]interface{}{ 101 | "code": m.code, 102 | "message": m.message, 103 | "allowed": m.Allowed, 104 | }) 105 | } 106 | 107 | func errorAsJSON(err Error) []byte { 108 | //nolint:errchkjson 109 | b, _ := json.Marshal(struct { 110 | Code int32 `json:"code"` 111 | Message string `json:"message"` 112 | }{err.Code(), err.Error()}) 113 | return b 114 | } 115 | 116 | func flattenComposite(errs *CompositeError) *CompositeError { 117 | var res []error 118 | for _, er := range errs.Errors { 119 | switch e := er.(type) { 120 | case *CompositeError: 121 | if e != nil && len(e.Errors) > 0 { 122 | flat := flattenComposite(e) 123 | if len(flat.Errors) > 0 { 124 | res = append(res, flat.Errors...) 125 | } 126 | } 127 | default: 128 | if e != nil { 129 | res = append(res, e) 130 | } 131 | } 132 | } 133 | return CompositeValidationError(res...) 134 | } 135 | 136 | // MethodNotAllowed creates a new method not allowed error 137 | func MethodNotAllowed(requested string, allow []string) Error { 138 | msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ",")) 139 | return &MethodNotAllowedError{ 140 | code: http.StatusMethodNotAllowed, 141 | Allowed: allow, 142 | message: msg, 143 | } 144 | } 145 | 146 | // ServeError implements the http error handler interface 147 | func ServeError(rw http.ResponseWriter, r *http.Request, err error) { 148 | rw.Header().Set("Content-Type", "application/json") 149 | switch e := err.(type) { 150 | case *CompositeError: 151 | er := flattenComposite(e) 152 | // strips composite errors to first element only 153 | if len(er.Errors) > 0 { 154 | ServeError(rw, r, er.Errors[0]) 155 | } else { 156 | // guard against empty CompositeError (invalid construct) 157 | ServeError(rw, r, nil) 158 | } 159 | case *MethodNotAllowedError: 160 | rw.Header().Add("Allow", strings.Join(e.Allowed, ",")) 161 | rw.WriteHeader(asHTTPCode(int(e.Code()))) 162 | if r == nil || r.Method != http.MethodHead { 163 | _, _ = rw.Write(errorAsJSON(e)) 164 | } 165 | case Error: 166 | value := reflect.ValueOf(e) 167 | if value.Kind() == reflect.Ptr && value.IsNil() { 168 | rw.WriteHeader(http.StatusInternalServerError) 169 | _, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error"))) 170 | return 171 | } 172 | rw.WriteHeader(asHTTPCode(int(e.Code()))) 173 | if r == nil || r.Method != http.MethodHead { 174 | _, _ = rw.Write(errorAsJSON(e)) 175 | } 176 | case nil: 177 | rw.WriteHeader(http.StatusInternalServerError) 178 | _, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error"))) 179 | default: 180 | rw.WriteHeader(http.StatusInternalServerError) 181 | if r == nil || r.Method != http.MethodHead { 182 | _, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error()))) 183 | } 184 | } 185 | } 186 | 187 | func asHTTPCode(input int) int { 188 | if input >= maximumValidHTTPCode { 189 | return DefaultHTTPCode 190 | } 191 | return input 192 | } 193 | -------------------------------------------------------------------------------- /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 | //nolint:err113 16 | package errors 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "net/http" 22 | "net/http/httptest" 23 | "strings" 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | type customError struct { 31 | apiError 32 | } 33 | 34 | func TestServeError(t *testing.T) { 35 | t.Run("method not allowed wins", func(t *testing.T) { 36 | // err abides by the Error interface 37 | err := MethodNotAllowed("GET", []string{"POST", "PUT"}) 38 | require.Error(t, err) 39 | 40 | recorder := httptest.NewRecorder() 41 | ServeError(recorder, nil, err) 42 | assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code) 43 | assert.Equal(t, "POST,PUT", recorder.Header().Get("Allow")) 44 | // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) 45 | assert.JSONEq(t, 46 | `{"code":405,"message":"method GET is not allowed, but [POST,PUT] are"}`, 47 | recorder.Body.String(), 48 | ) 49 | }) 50 | 51 | t.Run("renders status code from error", func(t *testing.T) { 52 | err := NotFound("") 53 | require.Error(t, err) 54 | 55 | recorder := httptest.NewRecorder() 56 | ServeError(recorder, nil, err) 57 | assert.Equal(t, http.StatusNotFound, recorder.Code) 58 | // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) 59 | assert.JSONEq(t, 60 | `{"code":404,"message":"Not found"}`, 61 | recorder.Body.String(), 62 | ) 63 | }) 64 | 65 | t.Run("renders mapped status code from error", func(t *testing.T) { 66 | // renders mapped status code from error when present 67 | err := InvalidTypeName("someType") 68 | require.Error(t, err) 69 | 70 | recorder := httptest.NewRecorder() 71 | ServeError(recorder, nil, err) 72 | assert.Equal(t, http.StatusUnprocessableEntity, recorder.Code) 73 | // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) 74 | assert.JSONEq(t, 75 | `{"code":601,"message":"someType is an invalid type name"}`, 76 | recorder.Body.String(), 77 | ) 78 | }) 79 | 80 | t.Run("overrides DefaultHTTPCode", func(t *testing.T) { 81 | func() { 82 | oldDefaultHTTPCode := DefaultHTTPCode 83 | defer func() { DefaultHTTPCode = oldDefaultHTTPCode }() 84 | DefaultHTTPCode = http.StatusBadRequest 85 | 86 | err := InvalidTypeName("someType") 87 | require.Error(t, err) 88 | 89 | recorder := httptest.NewRecorder() 90 | ServeError(recorder, nil, err) 91 | assert.Equal(t, http.StatusBadRequest, recorder.Code) 92 | // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) 93 | assert.JSONEq(t, 94 | `{"code":601,"message":"someType is an invalid type name"}`, 95 | recorder.Body.String(), 96 | ) 97 | }() 98 | }) 99 | 100 | t.Run("defaults to internal server error", func(t *testing.T) { 101 | simpleErr := errors.New("some error") 102 | recorder := httptest.NewRecorder() 103 | ServeError(recorder, nil, simpleErr) 104 | assert.Equal(t, http.StatusInternalServerError, recorder.Code) 105 | // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) 106 | assert.JSONEq(t, 107 | `{"code":500,"message":"some error"}`, 108 | recorder.Body.String(), 109 | ) 110 | }) 111 | 112 | t.Run("with composite erors", func(t *testing.T) { 113 | t.Run("unrecognized - return internal error with first error only - the second error is ignored", func(t *testing.T) { 114 | compositeErr := &CompositeError{ 115 | Errors: []error{ 116 | errors.New("firstError"), 117 | errors.New("anotherError"), 118 | }, 119 | } 120 | recorder := httptest.NewRecorder() 121 | ServeError(recorder, nil, compositeErr) 122 | assert.Equal(t, http.StatusInternalServerError, recorder.Code) 123 | assert.JSONEq(t, 124 | `{"code":500,"message":"firstError"}`, 125 | recorder.Body.String(), 126 | ) 127 | }) 128 | 129 | t.Run("recognized - return internal error with first error only - the second error is ignored", func(t *testing.T) { 130 | compositeErr := &CompositeError{ 131 | Errors: []error{ 132 | New(600, "myApiError"), 133 | New(601, "myOtherApiError"), 134 | }, 135 | } 136 | recorder := httptest.NewRecorder() 137 | ServeError(recorder, nil, compositeErr) 138 | assert.Equal(t, CompositeErrorCode, recorder.Code) 139 | assert.JSONEq(t, 140 | `{"code":600,"message":"myApiError"}`, 141 | recorder.Body.String(), 142 | ) 143 | }) 144 | 145 | t.Run("recognized API Error, flattened", func(t *testing.T) { 146 | compositeErr := &CompositeError{ 147 | Errors: []error{ 148 | &CompositeError{ 149 | Errors: []error{ 150 | New(600, "myApiError"), 151 | New(601, "myOtherApiError"), 152 | }, 153 | }, 154 | }, 155 | } 156 | recorder := httptest.NewRecorder() 157 | ServeError(recorder, nil, compositeErr) 158 | assert.Equal(t, CompositeErrorCode, recorder.Code) 159 | assert.JSONEq(t, 160 | `{"code":600,"message":"myApiError"}`, 161 | recorder.Body.String(), 162 | ) 163 | }) 164 | 165 | // (e.g. nil Error interface) 166 | t.Run("check guard against empty CompositeError", func(t *testing.T) { 167 | compositeErr := &CompositeError{ 168 | Errors: []error{ 169 | &CompositeError{ 170 | Errors: []error{}, 171 | }, 172 | }, 173 | } 174 | recorder := httptest.NewRecorder() 175 | ServeError(recorder, nil, compositeErr) 176 | assert.Equal(t, http.StatusInternalServerError, recorder.Code) 177 | assert.JSONEq(t, 178 | `{"code":500,"message":"Unknown error"}`, 179 | recorder.Body.String(), 180 | ) 181 | }) 182 | 183 | t.Run("check guard against nil type", func(t *testing.T) { 184 | recorder := httptest.NewRecorder() 185 | ServeError(recorder, nil, nil) 186 | assert.Equal(t, http.StatusInternalServerError, recorder.Code) 187 | assert.JSONEq(t, 188 | `{"code":500,"message":"Unknown error"}`, 189 | recorder.Body.String(), 190 | ) 191 | }) 192 | 193 | t.Run("check guard against nil value", func(t *testing.T) { 194 | recorder := httptest.NewRecorder() 195 | var z *customError 196 | ServeError(recorder, nil, z) 197 | assert.Equal(t, http.StatusInternalServerError, recorder.Code) 198 | assert.JSONEq(t, 199 | `{"code":500,"message":"Unknown error"}`, 200 | recorder.Body.String(), 201 | ) 202 | }) 203 | }) 204 | } 205 | 206 | func TestAPIErrors(t *testing.T) { 207 | err := New(402, "this failed %s", "yada") 208 | require.Error(t, err) 209 | assert.EqualValues(t, 402, err.Code()) 210 | assert.EqualValues(t, "this failed yada", err.Error()) 211 | 212 | err = NotFound("this failed %d", 1) 213 | require.Error(t, err) 214 | assert.EqualValues(t, http.StatusNotFound, err.Code()) 215 | assert.EqualValues(t, "this failed 1", err.Error()) 216 | 217 | err = NotFound("") 218 | require.Error(t, err) 219 | assert.EqualValues(t, http.StatusNotFound, err.Code()) 220 | assert.EqualValues(t, "Not found", err.Error()) 221 | 222 | err = NotImplemented("not implemented") 223 | require.Error(t, err) 224 | assert.EqualValues(t, http.StatusNotImplemented, err.Code()) 225 | assert.EqualValues(t, "not implemented", err.Error()) 226 | 227 | err = MethodNotAllowed("GET", []string{"POST", "PUT"}) 228 | require.Error(t, err) 229 | assert.EqualValues(t, http.StatusMethodNotAllowed, err.Code()) 230 | assert.EqualValues(t, "method GET is not allowed, but [POST,PUT] are", err.Error()) 231 | 232 | err = InvalidContentType("application/saml", []string{"application/json", "application/x-yaml"}) 233 | require.Error(t, err) 234 | assert.EqualValues(t, http.StatusUnsupportedMediaType, err.Code()) 235 | assert.EqualValues(t, "unsupported media type \"application/saml\", only [application/json application/x-yaml] are allowed", err.Error()) 236 | 237 | err = InvalidResponseFormat("application/saml", []string{"application/json", "application/x-yaml"}) 238 | require.Error(t, err) 239 | assert.EqualValues(t, http.StatusNotAcceptable, err.Code()) 240 | assert.EqualValues(t, "unsupported media type requested, only [application/json application/x-yaml] are available", err.Error()) 241 | } 242 | 243 | func TestValidateName(t *testing.T) { 244 | v := &Validation{Name: "myValidation", message: "myMessage"} 245 | 246 | // unchanged 247 | vv := v.ValidateName("") 248 | assert.EqualValues(t, "myValidation", vv.Name) 249 | assert.EqualValues(t, "myMessage", vv.message) 250 | 251 | // forced 252 | vv = v.ValidateName("myNewName") 253 | assert.EqualValues(t, "myNewName.myValidation", vv.Name) 254 | assert.EqualValues(t, "myNewName.myMessage", vv.message) 255 | 256 | v.Name = "" 257 | v.message = "myMessage" 258 | 259 | // unchanged 260 | vv = v.ValidateName("") 261 | assert.EqualValues(t, "", vv.Name) 262 | assert.EqualValues(t, "myMessage", vv.message) 263 | 264 | // forced 265 | vv = v.ValidateName("myNewName") 266 | assert.EqualValues(t, "myNewName", vv.Name) 267 | assert.EqualValues(t, "myNewNamemyMessage", vv.message) 268 | } 269 | 270 | func TestMarshalJSON(t *testing.T) { 271 | const ( 272 | expectedCode = http.StatusUnsupportedMediaType 273 | value = "myValue" 274 | ) 275 | list := []string{"a", "b"} 276 | 277 | e := InvalidContentType(value, list) 278 | 279 | jazon, err := e.MarshalJSON() 280 | require.NoError(t, err) 281 | 282 | expectedMessage := strings.ReplaceAll(fmt.Sprintf(contentTypeFail, value, list), `"`, `\"`) 283 | 284 | expectedJSON := fmt.Sprintf( 285 | `{"code":%d,"message":"%s","name":"Content-Type","in":"header","value":"%s","values":["a","b"]}`, 286 | expectedCode, expectedMessage, value, 287 | ) 288 | assert.JSONEq(t, expectedJSON, string(jazon)) 289 | 290 | a := apiError{code: 1, message: "a"} 291 | jazon, err = a.MarshalJSON() 292 | require.NoError(t, err) 293 | assert.JSONEq(t, `{"code":1,"message":"a"}`, string(jazon)) 294 | 295 | m := MethodNotAllowedError{code: 1, message: "a", Allowed: []string{"POST"}} 296 | jazon, err = m.MarshalJSON() 297 | require.NoError(t, err) 298 | assert.JSONEq(t, `{"code":1,"message":"a","allowed":["POST"]}`, string(jazon)) 299 | 300 | c := CompositeError{Errors: []error{e}, code: 1, message: "a"} 301 | jazon, err = c.MarshalJSON() 302 | require.NoError(t, err) 303 | assert.JSONEq(t, fmt.Sprintf(`{"code":1,"message":"a","errors":[%s]}`, expectedJSON), string(jazon)) 304 | 305 | p := ParseError{code: 1, message: "x", Name: "a", In: "b", Value: "c", Reason: errors.New("d")} 306 | jazon, err = p.MarshalJSON() 307 | require.NoError(t, err) 308 | assert.JSONEq(t, `{"code":1,"message":"x","name":"a","in":"b","value":"c","reason":"d"}`, string(jazon)) 309 | } 310 | -------------------------------------------------------------------------------- /auth.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 errors 16 | 17 | import "net/http" 18 | 19 | // Unauthenticated returns an unauthenticated error 20 | func Unauthenticated(scheme string) Error { 21 | return New(http.StatusUnauthorized, "unauthenticated for %s", scheme) 22 | } 23 | -------------------------------------------------------------------------------- /auth_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 errors 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestUnauthenticated(t *testing.T) { 24 | err := Unauthenticated("basic") 25 | assert.EqualValues(t, 401, err.Code()) 26 | assert.Equal(t, "unauthenticated for basic", err.Error()) 27 | } 28 | -------------------------------------------------------------------------------- /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 errors provides an Error interface and several concrete types 17 | implementing this interface to manage API errors and JSON-schema validation 18 | errors. 19 | 20 | A middleware handler ServeError() is provided to serve the errors types 21 | it defines. 22 | 23 | It is used throughout the various go-openapi toolkit libraries 24 | (https://github.com/go-openapi). 25 | */ 26 | package errors 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-openapi/errors 2 | 3 | require github.com/stretchr/testify v1.10.0 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/kr/text v0.2.0 // indirect 8 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 11 | gopkg.in/yaml.v3 v3.0.1 // indirect 12 | ) 13 | 14 | go 1.20 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 5 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 6 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 7 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 8 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 9 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 13 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 16 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /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 errors 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "net/http" 21 | ) 22 | 23 | // Validation represents a failure of a precondition 24 | type Validation struct { //nolint: errname 25 | code int32 26 | Name string 27 | In string 28 | Value interface{} 29 | message string 30 | Values []interface{} 31 | } 32 | 33 | func (e *Validation) Error() string { 34 | return e.message 35 | } 36 | 37 | // Code the error code 38 | func (e *Validation) Code() int32 { 39 | return e.code 40 | } 41 | 42 | // MarshalJSON implements the JSON encoding interface 43 | func (e Validation) MarshalJSON() ([]byte, error) { 44 | return json.Marshal(map[string]interface{}{ 45 | "code": e.code, 46 | "message": e.message, 47 | "in": e.In, 48 | "name": e.Name, 49 | "value": e.Value, 50 | "values": e.Values, 51 | }) 52 | } 53 | 54 | // ValidateName sets the name for a validation or updates it for a nested property 55 | func (e *Validation) ValidateName(name string) *Validation { 56 | if name != "" { 57 | if e.Name == "" { 58 | e.Name = name 59 | e.message = name + e.message 60 | } else { 61 | e.Name = name + "." + e.Name 62 | e.message = name + "." + e.message 63 | } 64 | } 65 | return e 66 | } 67 | 68 | const ( 69 | contentTypeFail = `unsupported media type %q, only %v are allowed` 70 | responseFormatFail = `unsupported media type requested, only %v are available` 71 | ) 72 | 73 | // InvalidContentType error for an invalid content type 74 | func InvalidContentType(value string, allowed []string) *Validation { 75 | values := make([]interface{}, 0, len(allowed)) 76 | for _, v := range allowed { 77 | values = append(values, v) 78 | } 79 | return &Validation{ 80 | code: http.StatusUnsupportedMediaType, 81 | Name: "Content-Type", 82 | In: "header", 83 | Value: value, 84 | Values: values, 85 | message: fmt.Sprintf(contentTypeFail, value, allowed), 86 | } 87 | } 88 | 89 | // InvalidResponseFormat error for an unacceptable response format request 90 | func InvalidResponseFormat(value string, allowed []string) *Validation { 91 | values := make([]interface{}, 0, len(allowed)) 92 | for _, v := range allowed { 93 | values = append(values, v) 94 | } 95 | return &Validation{ 96 | code: http.StatusNotAcceptable, 97 | Name: "Accept", 98 | In: "header", 99 | Value: value, 100 | Values: values, 101 | message: fmt.Sprintf(responseFormatFail, allowed), 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /middleware.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 errors 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "strings" 21 | ) 22 | 23 | // APIVerificationFailed is an error that contains all the missing info for a mismatched section 24 | // between the api registrations and the api spec 25 | type APIVerificationFailed struct { //nolint: errname 26 | Section string `json:"section,omitempty"` 27 | MissingSpecification []string `json:"missingSpecification,omitempty"` 28 | MissingRegistration []string `json:"missingRegistration,omitempty"` 29 | } 30 | 31 | func (v *APIVerificationFailed) Error() string { 32 | buf := bytes.NewBuffer(nil) 33 | 34 | hasRegMissing := len(v.MissingRegistration) > 0 35 | hasSpecMissing := len(v.MissingSpecification) > 0 36 | 37 | if hasRegMissing { 38 | buf.WriteString(fmt.Sprintf("missing [%s] %s registrations", strings.Join(v.MissingRegistration, ", "), v.Section)) 39 | } 40 | 41 | if hasRegMissing && hasSpecMissing { 42 | buf.WriteString("\n") 43 | } 44 | 45 | if hasSpecMissing { 46 | buf.WriteString(fmt.Sprintf("missing from spec file [%s] %s", strings.Join(v.MissingSpecification, ", "), v.Section)) 47 | } 48 | 49 | return buf.String() 50 | } 51 | -------------------------------------------------------------------------------- /middleware_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 errors 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestAPIVerificationFailed(t *testing.T) { 24 | err := &APIVerificationFailed{ 25 | Section: "consumer", 26 | MissingSpecification: []string{"application/json", "application/x-yaml"}, 27 | MissingRegistration: []string{"text/html", "application/xml"}, 28 | } 29 | 30 | expected := `missing [text/html, application/xml] consumer registrations 31 | missing from spec file [application/json, application/x-yaml] consumer` 32 | assert.Equal(t, expected, err.Error()) 33 | } 34 | -------------------------------------------------------------------------------- /parsing.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 errors 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "net/http" 21 | ) 22 | 23 | // ParseError represents a parsing error 24 | type ParseError struct { 25 | code int32 26 | Name string 27 | In string 28 | Value string 29 | Reason error 30 | message string 31 | } 32 | 33 | func (e *ParseError) Error() string { 34 | return e.message 35 | } 36 | 37 | // Code returns the http status code for this error 38 | func (e *ParseError) Code() int32 { 39 | return e.code 40 | } 41 | 42 | // MarshalJSON implements the JSON encoding interface 43 | func (e ParseError) MarshalJSON() ([]byte, error) { 44 | var reason string 45 | if e.Reason != nil { 46 | reason = e.Reason.Error() 47 | } 48 | return json.Marshal(map[string]interface{}{ 49 | "code": e.code, 50 | "message": e.message, 51 | "in": e.In, 52 | "name": e.Name, 53 | "value": e.Value, 54 | "reason": reason, 55 | }) 56 | } 57 | 58 | const ( 59 | parseErrorTemplContent = `parsing %s %s from %q failed, because %s` 60 | parseErrorTemplContentNoIn = `parsing %s from %q failed, because %s` 61 | ) 62 | 63 | // NewParseError creates a new parse error 64 | func NewParseError(name, in, value string, reason error) *ParseError { 65 | var msg string 66 | if in == "" { 67 | msg = fmt.Sprintf(parseErrorTemplContentNoIn, name, value, reason) 68 | } else { 69 | msg = fmt.Sprintf(parseErrorTemplContent, name, in, value, reason) 70 | } 71 | return &ParseError{ 72 | code: http.StatusBadRequest, 73 | Name: name, 74 | In: in, 75 | Value: value, 76 | Reason: reason, 77 | message: msg, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /parsing_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 | //nolint:err113 16 | package errors 17 | 18 | import ( 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestParseError(t *testing.T) { 26 | err := NewParseError("Content-Type", "header", "application(", errors.New("unable to parse")) 27 | assert.EqualValues(t, 400, err.Code()) 28 | assert.Equal(t, "parsing Content-Type header from \"application(\" failed, because unable to parse", err.Error()) 29 | 30 | err = NewParseError("Content-Type", "", "application(", errors.New("unable to parse")) 31 | assert.EqualValues(t, 400, err.Code()) 32 | assert.Equal(t, "parsing Content-Type from \"application(\" failed, because unable to parse", err.Error()) 33 | } 34 | -------------------------------------------------------------------------------- /schema.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 errors 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "net/http" 21 | "strings" 22 | ) 23 | 24 | const ( 25 | invalidType = "%s is an invalid type name" 26 | typeFail = "%s in %s must be of type %s" 27 | typeFailWithData = "%s in %s must be of type %s: %q" 28 | typeFailWithError = "%s in %s must be of type %s, because: %s" 29 | requiredFail = "%s in %s is required" 30 | readOnlyFail = "%s in %s is readOnly" 31 | tooLongMessage = "%s in %s should be at most %d chars long" 32 | tooShortMessage = "%s in %s should be at least %d chars long" 33 | patternFail = "%s in %s should match '%s'" 34 | enumFail = "%s in %s should be one of %v" 35 | multipleOfFail = "%s in %s should be a multiple of %v" 36 | maximumIncFail = "%s in %s should be less than or equal to %v" 37 | maximumExcFail = "%s in %s should be less than %v" 38 | minIncFail = "%s in %s should be greater than or equal to %v" 39 | minExcFail = "%s in %s should be greater than %v" 40 | uniqueFail = "%s in %s shouldn't contain duplicates" 41 | maximumItemsFail = "%s in %s should have at most %d items" 42 | minItemsFail = "%s in %s should have at least %d items" 43 | typeFailNoIn = "%s must be of type %s" 44 | typeFailWithDataNoIn = "%s must be of type %s: %q" 45 | typeFailWithErrorNoIn = "%s must be of type %s, because: %s" 46 | requiredFailNoIn = "%s is required" 47 | readOnlyFailNoIn = "%s is readOnly" 48 | tooLongMessageNoIn = "%s should be at most %d chars long" 49 | tooShortMessageNoIn = "%s should be at least %d chars long" 50 | patternFailNoIn = "%s should match '%s'" 51 | enumFailNoIn = "%s should be one of %v" 52 | multipleOfFailNoIn = "%s should be a multiple of %v" 53 | maximumIncFailNoIn = "%s should be less than or equal to %v" 54 | maximumExcFailNoIn = "%s should be less than %v" 55 | minIncFailNoIn = "%s should be greater than or equal to %v" 56 | minExcFailNoIn = "%s should be greater than %v" 57 | uniqueFailNoIn = "%s shouldn't contain duplicates" 58 | maximumItemsFailNoIn = "%s should have at most %d items" 59 | minItemsFailNoIn = "%s should have at least %d items" 60 | noAdditionalItems = "%s in %s can't have additional items" 61 | noAdditionalItemsNoIn = "%s can't have additional items" 62 | tooFewProperties = "%s in %s should have at least %d properties" 63 | tooFewPropertiesNoIn = "%s should have at least %d properties" 64 | tooManyProperties = "%s in %s should have at most %d properties" 65 | tooManyPropertiesNoIn = "%s should have at most %d properties" 66 | unallowedProperty = "%s.%s in %s is a forbidden property" 67 | unallowedPropertyNoIn = "%s.%s is a forbidden property" 68 | failedAllPatternProps = "%s.%s in %s failed all pattern properties" 69 | failedAllPatternPropsNoIn = "%s.%s failed all pattern properties" 70 | multipleOfMustBePositive = "factor MultipleOf declared for %s must be positive: %v" 71 | ) 72 | 73 | const maximumValidHTTPCode = 600 74 | 75 | // All code responses can be used to differentiate errors for different handling 76 | // by the consuming program 77 | const ( 78 | // CompositeErrorCode remains 422 for backwards-compatibility 79 | // and to separate it from validation errors with cause 80 | CompositeErrorCode = http.StatusUnprocessableEntity 81 | 82 | // InvalidTypeCode is used for any subclass of invalid types 83 | InvalidTypeCode = maximumValidHTTPCode + iota 84 | RequiredFailCode 85 | TooLongFailCode 86 | TooShortFailCode 87 | PatternFailCode 88 | EnumFailCode 89 | MultipleOfFailCode 90 | MaxFailCode 91 | MinFailCode 92 | UniqueFailCode 93 | MaxItemsFailCode 94 | MinItemsFailCode 95 | NoAdditionalItemsCode 96 | TooFewPropertiesCode 97 | TooManyPropertiesCode 98 | UnallowedPropertyCode 99 | FailedAllPatternPropsCode 100 | MultipleOfMustBePositiveCode 101 | ReadOnlyFailCode 102 | ) 103 | 104 | // CompositeError is an error that groups several errors together 105 | type CompositeError struct { 106 | Errors []error 107 | code int32 108 | message string 109 | } 110 | 111 | // Code for this error 112 | func (c *CompositeError) Code() int32 { 113 | return c.code 114 | } 115 | 116 | func (c *CompositeError) Error() string { 117 | if len(c.Errors) > 0 { 118 | msgs := []string{c.message + ":"} 119 | for _, e := range c.Errors { 120 | msgs = append(msgs, e.Error()) 121 | } 122 | return strings.Join(msgs, "\n") 123 | } 124 | return c.message 125 | } 126 | 127 | func (c *CompositeError) Unwrap() []error { 128 | return c.Errors 129 | } 130 | 131 | // MarshalJSON implements the JSON encoding interface 132 | func (c CompositeError) MarshalJSON() ([]byte, error) { 133 | return json.Marshal(map[string]interface{}{ 134 | "code": c.code, 135 | "message": c.message, 136 | "errors": c.Errors, 137 | }) 138 | } 139 | 140 | // CompositeValidationError an error to wrap a bunch of other errors 141 | func CompositeValidationError(errors ...error) *CompositeError { 142 | return &CompositeError{ 143 | code: CompositeErrorCode, 144 | Errors: append(make([]error, 0, len(errors)), errors...), 145 | message: "validation failure list", 146 | } 147 | } 148 | 149 | // ValidateName recursively sets the name for all validations or updates them for nested properties 150 | func (c *CompositeError) ValidateName(name string) *CompositeError { 151 | for i, e := range c.Errors { 152 | if ve, ok := e.(*Validation); ok { 153 | c.Errors[i] = ve.ValidateName(name) 154 | } else if ce, ok := e.(*CompositeError); ok { 155 | c.Errors[i] = ce.ValidateName(name) 156 | } 157 | } 158 | 159 | return c 160 | } 161 | 162 | // FailedAllPatternProperties an error for when the property doesn't match a pattern 163 | func FailedAllPatternProperties(name, in, key string) *Validation { 164 | msg := fmt.Sprintf(failedAllPatternProps, name, key, in) 165 | if in == "" { 166 | msg = fmt.Sprintf(failedAllPatternPropsNoIn, name, key) 167 | } 168 | return &Validation{ 169 | code: FailedAllPatternPropsCode, 170 | Name: name, 171 | In: in, 172 | Value: key, 173 | message: msg, 174 | } 175 | } 176 | 177 | // PropertyNotAllowed an error for when the property doesn't match a pattern 178 | func PropertyNotAllowed(name, in, key string) *Validation { 179 | msg := fmt.Sprintf(unallowedProperty, name, key, in) 180 | if in == "" { 181 | msg = fmt.Sprintf(unallowedPropertyNoIn, name, key) 182 | } 183 | return &Validation{ 184 | code: UnallowedPropertyCode, 185 | Name: name, 186 | In: in, 187 | Value: key, 188 | message: msg, 189 | } 190 | } 191 | 192 | // TooFewProperties an error for an object with too few properties 193 | func TooFewProperties(name, in string, n int64) *Validation { 194 | msg := fmt.Sprintf(tooFewProperties, name, in, n) 195 | if in == "" { 196 | msg = fmt.Sprintf(tooFewPropertiesNoIn, name, n) 197 | } 198 | return &Validation{ 199 | code: TooFewPropertiesCode, 200 | Name: name, 201 | In: in, 202 | Value: n, 203 | message: msg, 204 | } 205 | } 206 | 207 | // TooManyProperties an error for an object with too many properties 208 | func TooManyProperties(name, in string, n int64) *Validation { 209 | msg := fmt.Sprintf(tooManyProperties, name, in, n) 210 | if in == "" { 211 | msg = fmt.Sprintf(tooManyPropertiesNoIn, name, n) 212 | } 213 | return &Validation{ 214 | code: TooManyPropertiesCode, 215 | Name: name, 216 | In: in, 217 | Value: n, 218 | message: msg, 219 | } 220 | } 221 | 222 | // AdditionalItemsNotAllowed an error for invalid additional items 223 | func AdditionalItemsNotAllowed(name, in string) *Validation { 224 | msg := fmt.Sprintf(noAdditionalItems, name, in) 225 | if in == "" { 226 | msg = fmt.Sprintf(noAdditionalItemsNoIn, name) 227 | } 228 | return &Validation{ 229 | code: NoAdditionalItemsCode, 230 | Name: name, 231 | In: in, 232 | message: msg, 233 | } 234 | } 235 | 236 | // InvalidCollectionFormat another flavor of invalid type error 237 | func InvalidCollectionFormat(name, in, format string) *Validation { 238 | return &Validation{ 239 | code: InvalidTypeCode, 240 | Name: name, 241 | In: in, 242 | Value: format, 243 | message: fmt.Sprintf("the collection format %q is not supported for the %s param %q", format, in, name), 244 | } 245 | } 246 | 247 | // InvalidTypeName an error for when the type is invalid 248 | func InvalidTypeName(typeName string) *Validation { 249 | return &Validation{ 250 | code: InvalidTypeCode, 251 | Value: typeName, 252 | message: fmt.Sprintf(invalidType, typeName), 253 | } 254 | } 255 | 256 | // InvalidType creates an error for when the type is invalid 257 | func InvalidType(name, in, typeName string, value interface{}) *Validation { 258 | var message string 259 | 260 | if in != "" { 261 | switch value.(type) { 262 | case string: 263 | message = fmt.Sprintf(typeFailWithData, name, in, typeName, value) 264 | case error: 265 | message = fmt.Sprintf(typeFailWithError, name, in, typeName, value) 266 | default: 267 | message = fmt.Sprintf(typeFail, name, in, typeName) 268 | } 269 | } else { 270 | switch value.(type) { 271 | case string: 272 | message = fmt.Sprintf(typeFailWithDataNoIn, name, typeName, value) 273 | case error: 274 | message = fmt.Sprintf(typeFailWithErrorNoIn, name, typeName, value) 275 | default: 276 | message = fmt.Sprintf(typeFailNoIn, name, typeName) 277 | } 278 | } 279 | 280 | return &Validation{ 281 | code: InvalidTypeCode, 282 | Name: name, 283 | In: in, 284 | Value: value, 285 | message: message, 286 | } 287 | 288 | } 289 | 290 | // DuplicateItems error for when an array contains duplicates 291 | func DuplicateItems(name, in string) *Validation { 292 | msg := fmt.Sprintf(uniqueFail, name, in) 293 | if in == "" { 294 | msg = fmt.Sprintf(uniqueFailNoIn, name) 295 | } 296 | return &Validation{ 297 | code: UniqueFailCode, 298 | Name: name, 299 | In: in, 300 | message: msg, 301 | } 302 | } 303 | 304 | // TooManyItems error for when an array contains too many items 305 | func TooManyItems(name, in string, maximum int64, value interface{}) *Validation { 306 | msg := fmt.Sprintf(maximumItemsFail, name, in, maximum) 307 | if in == "" { 308 | msg = fmt.Sprintf(maximumItemsFailNoIn, name, maximum) 309 | } 310 | 311 | return &Validation{ 312 | code: MaxItemsFailCode, 313 | Name: name, 314 | In: in, 315 | Value: value, 316 | message: msg, 317 | } 318 | } 319 | 320 | // TooFewItems error for when an array contains too few items 321 | func TooFewItems(name, in string, minimum int64, value interface{}) *Validation { 322 | msg := fmt.Sprintf(minItemsFail, name, in, minimum) 323 | if in == "" { 324 | msg = fmt.Sprintf(minItemsFailNoIn, name, minimum) 325 | } 326 | return &Validation{ 327 | code: MinItemsFailCode, 328 | Name: name, 329 | In: in, 330 | Value: value, 331 | message: msg, 332 | } 333 | } 334 | 335 | // ExceedsMaximumInt error for when maximumimum validation fails 336 | func ExceedsMaximumInt(name, in string, maximum int64, exclusive bool, value interface{}) *Validation { 337 | var message string 338 | if in == "" { 339 | m := maximumIncFailNoIn 340 | if exclusive { 341 | m = maximumExcFailNoIn 342 | } 343 | message = fmt.Sprintf(m, name, maximum) 344 | } else { 345 | m := maximumIncFail 346 | if exclusive { 347 | m = maximumExcFail 348 | } 349 | message = fmt.Sprintf(m, name, in, maximum) 350 | } 351 | return &Validation{ 352 | code: MaxFailCode, 353 | Name: name, 354 | In: in, 355 | Value: value, 356 | message: message, 357 | } 358 | } 359 | 360 | // ExceedsMaximumUint error for when maximumimum validation fails 361 | func ExceedsMaximumUint(name, in string, maximum uint64, exclusive bool, value interface{}) *Validation { 362 | var message string 363 | if in == "" { 364 | m := maximumIncFailNoIn 365 | if exclusive { 366 | m = maximumExcFailNoIn 367 | } 368 | message = fmt.Sprintf(m, name, maximum) 369 | } else { 370 | m := maximumIncFail 371 | if exclusive { 372 | m = maximumExcFail 373 | } 374 | message = fmt.Sprintf(m, name, in, maximum) 375 | } 376 | return &Validation{ 377 | code: MaxFailCode, 378 | Name: name, 379 | In: in, 380 | Value: value, 381 | message: message, 382 | } 383 | } 384 | 385 | // ExceedsMaximum error for when maximumimum validation fails 386 | func ExceedsMaximum(name, in string, maximum float64, exclusive bool, value interface{}) *Validation { 387 | var message string 388 | if in == "" { 389 | m := maximumIncFailNoIn 390 | if exclusive { 391 | m = maximumExcFailNoIn 392 | } 393 | message = fmt.Sprintf(m, name, maximum) 394 | } else { 395 | m := maximumIncFail 396 | if exclusive { 397 | m = maximumExcFail 398 | } 399 | message = fmt.Sprintf(m, name, in, maximum) 400 | } 401 | return &Validation{ 402 | code: MaxFailCode, 403 | Name: name, 404 | In: in, 405 | Value: value, 406 | message: message, 407 | } 408 | } 409 | 410 | // ExceedsMinimumInt error for when minimum validation fails 411 | func ExceedsMinimumInt(name, in string, minimum int64, exclusive bool, value interface{}) *Validation { 412 | var message string 413 | if in == "" { 414 | m := minIncFailNoIn 415 | if exclusive { 416 | m = minExcFailNoIn 417 | } 418 | message = fmt.Sprintf(m, name, minimum) 419 | } else { 420 | m := minIncFail 421 | if exclusive { 422 | m = minExcFail 423 | } 424 | message = fmt.Sprintf(m, name, in, minimum) 425 | } 426 | return &Validation{ 427 | code: MinFailCode, 428 | Name: name, 429 | In: in, 430 | Value: value, 431 | message: message, 432 | } 433 | } 434 | 435 | // ExceedsMinimumUint error for when minimum validation fails 436 | func ExceedsMinimumUint(name, in string, minimum uint64, exclusive bool, value interface{}) *Validation { 437 | var message string 438 | if in == "" { 439 | m := minIncFailNoIn 440 | if exclusive { 441 | m = minExcFailNoIn 442 | } 443 | message = fmt.Sprintf(m, name, minimum) 444 | } else { 445 | m := minIncFail 446 | if exclusive { 447 | m = minExcFail 448 | } 449 | message = fmt.Sprintf(m, name, in, minimum) 450 | } 451 | return &Validation{ 452 | code: MinFailCode, 453 | Name: name, 454 | In: in, 455 | Value: value, 456 | message: message, 457 | } 458 | } 459 | 460 | // ExceedsMinimum error for when minimum validation fails 461 | func ExceedsMinimum(name, in string, minimum float64, exclusive bool, value interface{}) *Validation { 462 | var message string 463 | if in == "" { 464 | m := minIncFailNoIn 465 | if exclusive { 466 | m = minExcFailNoIn 467 | } 468 | message = fmt.Sprintf(m, name, minimum) 469 | } else { 470 | m := minIncFail 471 | if exclusive { 472 | m = minExcFail 473 | } 474 | message = fmt.Sprintf(m, name, in, minimum) 475 | } 476 | return &Validation{ 477 | code: MinFailCode, 478 | Name: name, 479 | In: in, 480 | Value: value, 481 | message: message, 482 | } 483 | } 484 | 485 | // NotMultipleOf error for when multiple of validation fails 486 | func NotMultipleOf(name, in string, multiple, value interface{}) *Validation { 487 | var msg string 488 | if in == "" { 489 | msg = fmt.Sprintf(multipleOfFailNoIn, name, multiple) 490 | } else { 491 | msg = fmt.Sprintf(multipleOfFail, name, in, multiple) 492 | } 493 | return &Validation{ 494 | code: MultipleOfFailCode, 495 | Name: name, 496 | In: in, 497 | Value: value, 498 | message: msg, 499 | } 500 | } 501 | 502 | // EnumFail error for when an enum validation fails 503 | func EnumFail(name, in string, value interface{}, values []interface{}) *Validation { 504 | var msg string 505 | if in == "" { 506 | msg = fmt.Sprintf(enumFailNoIn, name, values) 507 | } else { 508 | msg = fmt.Sprintf(enumFail, name, in, values) 509 | } 510 | 511 | return &Validation{ 512 | code: EnumFailCode, 513 | Name: name, 514 | In: in, 515 | Value: value, 516 | Values: values, 517 | message: msg, 518 | } 519 | } 520 | 521 | // Required error for when a value is missing 522 | func Required(name, in string, value interface{}) *Validation { 523 | var msg string 524 | if in == "" { 525 | msg = fmt.Sprintf(requiredFailNoIn, name) 526 | } else { 527 | msg = fmt.Sprintf(requiredFail, name, in) 528 | } 529 | return &Validation{ 530 | code: RequiredFailCode, 531 | Name: name, 532 | In: in, 533 | Value: value, 534 | message: msg, 535 | } 536 | } 537 | 538 | // ReadOnly error for when a value is present in request 539 | func ReadOnly(name, in string, value interface{}) *Validation { 540 | var msg string 541 | if in == "" { 542 | msg = fmt.Sprintf(readOnlyFailNoIn, name) 543 | } else { 544 | msg = fmt.Sprintf(readOnlyFail, name, in) 545 | } 546 | return &Validation{ 547 | code: ReadOnlyFailCode, 548 | Name: name, 549 | In: in, 550 | Value: value, 551 | message: msg, 552 | } 553 | } 554 | 555 | // TooLong error for when a string is too long 556 | func TooLong(name, in string, maximum int64, value interface{}) *Validation { 557 | var msg string 558 | if in == "" { 559 | msg = fmt.Sprintf(tooLongMessageNoIn, name, maximum) 560 | } else { 561 | msg = fmt.Sprintf(tooLongMessage, name, in, maximum) 562 | } 563 | return &Validation{ 564 | code: TooLongFailCode, 565 | Name: name, 566 | In: in, 567 | Value: value, 568 | message: msg, 569 | } 570 | } 571 | 572 | // TooShort error for when a string is too short 573 | func TooShort(name, in string, minimum int64, value interface{}) *Validation { 574 | var msg string 575 | if in == "" { 576 | msg = fmt.Sprintf(tooShortMessageNoIn, name, minimum) 577 | } else { 578 | msg = fmt.Sprintf(tooShortMessage, name, in, minimum) 579 | } 580 | 581 | return &Validation{ 582 | code: TooShortFailCode, 583 | Name: name, 584 | In: in, 585 | Value: value, 586 | message: msg, 587 | } 588 | } 589 | 590 | // FailedPattern error for when a string fails a regex pattern match 591 | // the pattern that is returned is the ECMA syntax version of the pattern not the golang version. 592 | func FailedPattern(name, in, pattern string, value interface{}) *Validation { 593 | var msg string 594 | if in == "" { 595 | msg = fmt.Sprintf(patternFailNoIn, name, pattern) 596 | } else { 597 | msg = fmt.Sprintf(patternFail, name, in, pattern) 598 | } 599 | 600 | return &Validation{ 601 | code: PatternFailCode, 602 | Name: name, 603 | In: in, 604 | Value: value, 605 | message: msg, 606 | } 607 | } 608 | 609 | // MultipleOfMustBePositive error for when a 610 | // multipleOf factor is negative 611 | func MultipleOfMustBePositive(name, in string, factor interface{}) *Validation { 612 | return &Validation{ 613 | code: MultipleOfMustBePositiveCode, 614 | Name: name, 615 | In: in, 616 | Value: factor, 617 | message: fmt.Sprintf(multipleOfMustBePositive, name, factor), 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /schema_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 | //nolint:err113 16 | package errors 17 | 18 | import ( 19 | "errors" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | //nolint:maintidx 27 | func TestSchemaErrors(t *testing.T) { 28 | t.Run("with InvalidType", func(t *testing.T) { 29 | err := InvalidType("confirmed", "query", "boolean", nil) 30 | require.Error(t, err) 31 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 32 | assert.Equal(t, "confirmed in query must be of type boolean", err.Error()) 33 | 34 | err = InvalidType("confirmed", "", "boolean", nil) 35 | require.Error(t, err) 36 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 37 | assert.Equal(t, "confirmed must be of type boolean", err.Error()) 38 | 39 | err = InvalidType("confirmed", "query", "boolean", "hello") 40 | require.Error(t, err) 41 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 42 | assert.Equal(t, "confirmed in query must be of type boolean: \"hello\"", err.Error()) 43 | 44 | err = InvalidType("confirmed", "query", "boolean", errors.New("hello")) 45 | require.Error(t, err) 46 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 47 | assert.Equal(t, "confirmed in query must be of type boolean, because: hello", err.Error()) 48 | 49 | err = InvalidType("confirmed", "", "boolean", "hello") 50 | require.Error(t, err) 51 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 52 | assert.Equal(t, "confirmed must be of type boolean: \"hello\"", err.Error()) 53 | 54 | err = InvalidType("confirmed", "", "boolean", errors.New("hello")) 55 | require.Error(t, err) 56 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 57 | assert.Equal(t, "confirmed must be of type boolean, because: hello", err.Error()) 58 | }) 59 | 60 | t.Run("with DuplicateItems", func(t *testing.T) { 61 | err := DuplicateItems("uniques", "query") 62 | require.Error(t, err) 63 | assert.EqualValues(t, UniqueFailCode, err.Code()) 64 | assert.Equal(t, "uniques in query shouldn't contain duplicates", err.Error()) 65 | 66 | err = DuplicateItems("uniques", "") 67 | require.Error(t, err) 68 | assert.EqualValues(t, UniqueFailCode, err.Code()) 69 | assert.Equal(t, "uniques shouldn't contain duplicates", err.Error()) 70 | }) 71 | 72 | t.Run("with TooMany/TooFew Items", func(t *testing.T) { 73 | err := TooManyItems("something", "query", 5, 6) 74 | require.Error(t, err) 75 | assert.EqualValues(t, MaxItemsFailCode, err.Code()) 76 | assert.Equal(t, "something in query should have at most 5 items", err.Error()) 77 | assert.Equal(t, 6, err.Value) 78 | 79 | err = TooManyItems("something", "", 5, 6) 80 | require.Error(t, err) 81 | assert.EqualValues(t, MaxItemsFailCode, err.Code()) 82 | assert.Equal(t, "something should have at most 5 items", err.Error()) 83 | assert.Equal(t, 6, err.Value) 84 | 85 | err = TooFewItems("something", "", 5, 4) 86 | require.Error(t, err) 87 | assert.EqualValues(t, MinItemsFailCode, err.Code()) 88 | assert.Equal(t, "something should have at least 5 items", err.Error()) 89 | assert.Equal(t, 4, err.Value) 90 | }) 91 | 92 | t.Run("with ExceedsMaximum", func(t *testing.T) { 93 | err := ExceedsMaximumInt("something", "query", 5, false, 6) 94 | require.Error(t, err) 95 | assert.EqualValues(t, MaxFailCode, err.Code()) 96 | assert.Equal(t, "something in query should be less than or equal to 5", err.Error()) 97 | assert.Equal(t, 6, err.Value) 98 | 99 | err = ExceedsMaximumInt("something", "", 5, false, 6) 100 | require.Error(t, err) 101 | assert.EqualValues(t, MaxFailCode, err.Code()) 102 | assert.Equal(t, "something should be less than or equal to 5", err.Error()) 103 | assert.Equal(t, 6, err.Value) 104 | 105 | err = ExceedsMaximumInt("something", "query", 5, true, 6) 106 | require.Error(t, err) 107 | assert.EqualValues(t, MaxFailCode, err.Code()) 108 | assert.Equal(t, "something in query should be less than 5", err.Error()) 109 | assert.Equal(t, 6, err.Value) 110 | 111 | err = ExceedsMaximumInt("something", "", 5, true, 6) 112 | require.Error(t, err) 113 | assert.EqualValues(t, MaxFailCode, err.Code()) 114 | assert.Equal(t, "something should be less than 5", err.Error()) 115 | assert.Equal(t, 6, err.Value) 116 | 117 | err = ExceedsMaximumUint("something", "query", 5, false, 6) 118 | require.Error(t, err) 119 | assert.EqualValues(t, MaxFailCode, err.Code()) 120 | assert.Equal(t, "something in query should be less than or equal to 5", err.Error()) 121 | assert.Equal(t, 6, err.Value) 122 | 123 | err = ExceedsMaximumUint("something", "", 5, false, 6) 124 | require.Error(t, err) 125 | assert.EqualValues(t, MaxFailCode, err.Code()) 126 | assert.Equal(t, "something should be less than or equal to 5", err.Error()) 127 | assert.Equal(t, 6, err.Value) 128 | 129 | err = ExceedsMaximumUint("something", "query", 5, true, 6) 130 | require.Error(t, err) 131 | assert.EqualValues(t, MaxFailCode, err.Code()) 132 | assert.Equal(t, "something in query should be less than 5", err.Error()) 133 | assert.Equal(t, 6, err.Value) 134 | 135 | err = ExceedsMaximumUint("something", "", 5, true, 6) 136 | require.Error(t, err) 137 | assert.EqualValues(t, MaxFailCode, err.Code()) 138 | assert.Equal(t, "something should be less than 5", err.Error()) 139 | assert.Equal(t, 6, err.Value) 140 | 141 | err = ExceedsMaximum("something", "query", 5, false, 6) 142 | require.Error(t, err) 143 | assert.EqualValues(t, MaxFailCode, err.Code()) 144 | assert.Equal(t, "something in query should be less than or equal to 5", err.Error()) 145 | assert.Equal(t, 6, err.Value) 146 | 147 | err = ExceedsMaximum("something", "", 5, false, 6) 148 | require.Error(t, err) 149 | assert.EqualValues(t, MaxFailCode, err.Code()) 150 | assert.Equal(t, "something should be less than or equal to 5", err.Error()) 151 | assert.Equal(t, 6, err.Value) 152 | 153 | err = ExceedsMaximum("something", "query", 5, true, 6) 154 | require.Error(t, err) 155 | assert.EqualValues(t, MaxFailCode, err.Code()) 156 | assert.Equal(t, "something in query should be less than 5", err.Error()) 157 | assert.Equal(t, 6, err.Value) 158 | 159 | err = ExceedsMaximum("something", "", 5, true, 6) 160 | require.Error(t, err) 161 | assert.EqualValues(t, MaxFailCode, err.Code()) 162 | assert.Equal(t, "something should be less than 5", err.Error()) 163 | assert.Equal(t, 6, err.Value) 164 | }) 165 | 166 | t.Run("with ExceedsMinimum", func(t *testing.T) { 167 | err := ExceedsMinimumInt("something", "query", 5, false, 4) 168 | require.Error(t, err) 169 | assert.EqualValues(t, MinFailCode, err.Code()) 170 | assert.Equal(t, "something in query should be greater than or equal to 5", err.Error()) 171 | assert.Equal(t, 4, err.Value) 172 | 173 | err = ExceedsMinimumInt("something", "", 5, false, 4) 174 | require.Error(t, err) 175 | assert.EqualValues(t, MinFailCode, err.Code()) 176 | assert.Equal(t, "something should be greater than or equal to 5", err.Error()) 177 | assert.Equal(t, 4, err.Value) 178 | 179 | err = ExceedsMinimumInt("something", "query", 5, true, 4) 180 | require.Error(t, err) 181 | assert.EqualValues(t, MinFailCode, err.Code()) 182 | assert.Equal(t, "something in query should be greater than 5", err.Error()) 183 | assert.Equal(t, 4, err.Value) 184 | 185 | err = ExceedsMinimumInt("something", "", 5, true, 4) 186 | require.Error(t, err) 187 | assert.EqualValues(t, MinFailCode, err.Code()) 188 | assert.Equal(t, "something should be greater than 5", err.Error()) 189 | assert.Equal(t, 4, err.Value) 190 | 191 | err = ExceedsMinimumUint("something", "query", 5, false, 4) 192 | require.Error(t, err) 193 | assert.EqualValues(t, MinFailCode, err.Code()) 194 | assert.Equal(t, "something in query should be greater than or equal to 5", err.Error()) 195 | assert.Equal(t, 4, err.Value) 196 | 197 | err = ExceedsMinimumUint("something", "", 5, false, 4) 198 | require.Error(t, err) 199 | assert.EqualValues(t, MinFailCode, err.Code()) 200 | assert.Equal(t, "something should be greater than or equal to 5", err.Error()) 201 | assert.Equal(t, 4, err.Value) 202 | 203 | err = ExceedsMinimumUint("something", "query", 5, true, 4) 204 | require.Error(t, err) 205 | assert.EqualValues(t, MinFailCode, err.Code()) 206 | assert.Equal(t, "something in query should be greater than 5", err.Error()) 207 | assert.Equal(t, 4, err.Value) 208 | 209 | err = ExceedsMinimumUint("something", "", 5, true, 4) 210 | require.Error(t, err) 211 | assert.EqualValues(t, MinFailCode, err.Code()) 212 | assert.Equal(t, "something should be greater than 5", err.Error()) 213 | assert.Equal(t, 4, err.Value) 214 | 215 | err = ExceedsMinimum("something", "query", 5, false, 4) 216 | require.Error(t, err) 217 | assert.EqualValues(t, MinFailCode, err.Code()) 218 | assert.Equal(t, "something in query should be greater than or equal to 5", err.Error()) 219 | assert.Equal(t, 4, err.Value) 220 | 221 | err = ExceedsMinimum("something", "", 5, false, 4) 222 | require.Error(t, err) 223 | assert.EqualValues(t, MinFailCode, err.Code()) 224 | assert.Equal(t, "something should be greater than or equal to 5", err.Error()) 225 | assert.Equal(t, 4, err.Value) 226 | 227 | err = ExceedsMinimum("something", "query", 5, true, 4) 228 | require.Error(t, err) 229 | assert.EqualValues(t, MinFailCode, err.Code()) 230 | assert.Equal(t, "something in query should be greater than 5", err.Error()) 231 | assert.Equal(t, 4, err.Value) 232 | 233 | err = ExceedsMinimum("something", "", 5, true, 4) 234 | require.Error(t, err) 235 | assert.EqualValues(t, MinFailCode, err.Code()) 236 | assert.Equal(t, "something should be greater than 5", err.Error()) 237 | assert.Equal(t, 4, err.Value) 238 | 239 | err = NotMultipleOf("something", "query", 5, 1) 240 | require.Error(t, err) 241 | assert.EqualValues(t, MultipleOfFailCode, err.Code()) 242 | assert.Equal(t, "something in query should be a multiple of 5", err.Error()) 243 | assert.Equal(t, 1, err.Value) 244 | }) 245 | 246 | t.Run("with MultipleOf", func(t *testing.T) { 247 | err := NotMultipleOf("something", "query", float64(5), float64(1)) 248 | require.Error(t, err) 249 | assert.EqualValues(t, MultipleOfFailCode, err.Code()) 250 | assert.Equal(t, "something in query should be a multiple of 5", err.Error()) 251 | assert.InDelta(t, float64(1), err.Value, 1e-6) 252 | 253 | err = NotMultipleOf("something", "query", uint64(5), uint64(1)) 254 | require.Error(t, err) 255 | assert.EqualValues(t, MultipleOfFailCode, err.Code()) 256 | assert.Equal(t, "something in query should be a multiple of 5", err.Error()) 257 | assert.Equal(t, uint64(1), err.Value) 258 | 259 | err = NotMultipleOf("something", "", 5, 1) 260 | require.Error(t, err) 261 | assert.EqualValues(t, MultipleOfFailCode, err.Code()) 262 | assert.Equal(t, "something should be a multiple of 5", err.Error()) 263 | assert.Equal(t, 1, err.Value) 264 | 265 | err = MultipleOfMustBePositive("path", "body", float64(-10)) 266 | require.Error(t, err) 267 | assert.EqualValues(t, MultipleOfMustBePositiveCode, err.Code()) 268 | assert.Equal(t, `factor MultipleOf declared for path must be positive: -10`, err.Error()) 269 | assert.InDelta(t, float64(-10), err.Value, 1e-6) 270 | 271 | err = MultipleOfMustBePositive("path", "body", int64(-10)) 272 | require.Error(t, err) 273 | assert.EqualValues(t, MultipleOfMustBePositiveCode, err.Code()) 274 | assert.Equal(t, `factor MultipleOf declared for path must be positive: -10`, err.Error()) 275 | assert.Equal(t, int64(-10), err.Value) 276 | }) 277 | 278 | t.Run("with EnumFail", func(t *testing.T) { 279 | err := EnumFail("something", "query", "yada", []interface{}{"hello", "world"}) 280 | require.Error(t, err) 281 | assert.EqualValues(t, EnumFailCode, err.Code()) 282 | assert.Equal(t, "something in query should be one of [hello world]", err.Error()) 283 | assert.Equal(t, "yada", err.Value) 284 | 285 | err = EnumFail("something", "", "yada", []interface{}{"hello", "world"}) 286 | require.Error(t, err) 287 | assert.EqualValues(t, EnumFailCode, err.Code()) 288 | assert.Equal(t, "something should be one of [hello world]", err.Error()) 289 | assert.Equal(t, "yada", err.Value) 290 | }) 291 | 292 | t.Run("with Required", func(t *testing.T) { 293 | err := Required("something", "query", nil) 294 | require.Error(t, err) 295 | assert.EqualValues(t, RequiredFailCode, err.Code()) 296 | assert.Equal(t, "something in query is required", err.Error()) 297 | assert.Nil(t, err.Value) 298 | 299 | err = Required("something", "", nil) 300 | require.Error(t, err) 301 | assert.EqualValues(t, RequiredFailCode, err.Code()) 302 | assert.Equal(t, "something is required", err.Error()) 303 | assert.Nil(t, err.Value) 304 | }) 305 | 306 | t.Run("with ReadOnly", func(t *testing.T) { 307 | err := ReadOnly("something", "query", nil) 308 | require.Error(t, err) 309 | assert.EqualValues(t, ReadOnlyFailCode, err.Code()) 310 | assert.Equal(t, "something in query is readOnly", err.Error()) 311 | assert.Nil(t, err.Value) 312 | 313 | err = ReadOnly("something", "", nil) 314 | require.Error(t, err) 315 | assert.EqualValues(t, ReadOnlyFailCode, err.Code()) 316 | assert.Equal(t, "something is readOnly", err.Error()) 317 | assert.Nil(t, err.Value) 318 | }) 319 | 320 | t.Run("with TooLong/TooShort", func(t *testing.T) { 321 | err := TooLong("something", "query", 5, "abcdef") 322 | require.Error(t, err) 323 | assert.EqualValues(t, TooLongFailCode, err.Code()) 324 | assert.Equal(t, "something in query should be at most 5 chars long", err.Error()) 325 | assert.Equal(t, "abcdef", err.Value) 326 | 327 | err = TooLong("something", "", 5, "abcdef") 328 | require.Error(t, err) 329 | assert.EqualValues(t, TooLongFailCode, err.Code()) 330 | assert.Equal(t, "something should be at most 5 chars long", err.Error()) 331 | assert.Equal(t, "abcdef", err.Value) 332 | 333 | err = TooShort("something", "query", 5, "a") 334 | require.Error(t, err) 335 | assert.EqualValues(t, TooShortFailCode, err.Code()) 336 | assert.Equal(t, "something in query should be at least 5 chars long", err.Error()) 337 | assert.Equal(t, "a", err.Value) 338 | 339 | err = TooShort("something", "", 5, "a") 340 | require.Error(t, err) 341 | assert.EqualValues(t, TooShortFailCode, err.Code()) 342 | assert.Equal(t, "something should be at least 5 chars long", err.Error()) 343 | assert.Equal(t, "a", err.Value) 344 | }) 345 | 346 | t.Run("with FailedPattern", func(t *testing.T) { 347 | err := FailedPattern("something", "query", "\\d+", "a") 348 | require.Error(t, err) 349 | assert.EqualValues(t, PatternFailCode, err.Code()) 350 | assert.Equal(t, "something in query should match '\\d+'", err.Error()) 351 | assert.Equal(t, "a", err.Value) 352 | 353 | err = FailedPattern("something", "", "\\d+", "a") 354 | require.Error(t, err) 355 | assert.EqualValues(t, PatternFailCode, err.Code()) 356 | assert.Equal(t, "something should match '\\d+'", err.Error()) 357 | assert.Equal(t, "a", err.Value) 358 | }) 359 | 360 | t.Run("with InvalidType", func(t *testing.T) { 361 | err := InvalidTypeName("something") 362 | require.Error(t, err) 363 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 364 | assert.Equal(t, "something is an invalid type name", err.Error()) 365 | }) 366 | 367 | t.Run("with AdditionalItemsNotAllowed", func(t *testing.T) { 368 | err := AdditionalItemsNotAllowed("something", "query") 369 | require.Error(t, err) 370 | assert.EqualValues(t, NoAdditionalItemsCode, err.Code()) 371 | assert.Equal(t, "something in query can't have additional items", err.Error()) 372 | 373 | err = AdditionalItemsNotAllowed("something", "") 374 | require.Error(t, err) 375 | assert.EqualValues(t, NoAdditionalItemsCode, err.Code()) 376 | assert.Equal(t, "something can't have additional items", err.Error()) 377 | }) 378 | 379 | err := InvalidCollectionFormat("something", "query", "yada") 380 | require.Error(t, err) 381 | assert.EqualValues(t, InvalidTypeCode, err.Code()) 382 | assert.Equal(t, "the collection format \"yada\" is not supported for the query param \"something\"", err.Error()) 383 | 384 | t.Run("with CompositeValidationError", func(t *testing.T) { 385 | err := CompositeValidationError() 386 | require.Error(t, err) 387 | assert.EqualValues(t, CompositeErrorCode, err.Code()) 388 | assert.Equal(t, "validation failure list", err.Error()) 389 | 390 | testErr1 := errors.New("first error") 391 | testErr2 := errors.New("second error") 392 | err = CompositeValidationError(testErr1, testErr2) 393 | require.Error(t, err) 394 | assert.EqualValues(t, CompositeErrorCode, err.Code()) 395 | assert.Equal(t, "validation failure list:\nfirst error\nsecond error", err.Error()) 396 | 397 | require.ErrorIs(t, err, testErr1) 398 | require.ErrorIs(t, err, testErr2) 399 | }) 400 | 401 | t.Run("should set validation name in CompositeValidation error", func(t *testing.T) { 402 | err := CompositeValidationError( 403 | InvalidContentType("text/html", []string{"application/json"}), 404 | CompositeValidationError( 405 | InvalidTypeName("y"), 406 | ), 407 | ) 408 | _ = err.ValidateName("new-name") 409 | const expectedMessage = `validation failure list: 410 | new-name.unsupported media type "text/html", only [application/json] are allowed 411 | validation failure list: 412 | new-namey is an invalid type name` 413 | assert.Equal(t, expectedMessage, err.Error()) 414 | }) 415 | 416 | t.Run("with PropertyNotAllowed", func(t *testing.T) { 417 | err = PropertyNotAllowed("path", "body", "key") 418 | require.Error(t, err) 419 | assert.EqualValues(t, UnallowedPropertyCode, err.Code()) 420 | // unallowedProperty = "%s.%s in %s is a forbidden property" 421 | assert.Equal(t, "path.key in body is a forbidden property", err.Error()) 422 | 423 | err = PropertyNotAllowed("path", "", "key") 424 | require.Error(t, err) 425 | assert.EqualValues(t, UnallowedPropertyCode, err.Code()) 426 | // unallowedPropertyNoIn = "%s.%s is a forbidden property" 427 | assert.Equal(t, "path.key is a forbidden property", err.Error()) 428 | }) 429 | 430 | t.Run("with TooMany/TooFew properties", func(t *testing.T) { 431 | err := TooManyProperties("path", "body", 10) 432 | require.Error(t, err) 433 | assert.EqualValues(t, TooManyPropertiesCode, err.Code()) 434 | // tooManyProperties = "%s in %s should have at most %d properties" 435 | assert.Equal(t, "path in body should have at most 10 properties", err.Error()) 436 | 437 | err = TooManyProperties("path", "", 10) 438 | require.Error(t, err) 439 | assert.EqualValues(t, TooManyPropertiesCode, err.Code()) 440 | // tooManyPropertiesNoIn = "%s should have at most %d properties" 441 | assert.Equal(t, "path should have at most 10 properties", err.Error()) 442 | 443 | err = TooFewProperties("path", "body", 10) 444 | require.Error(t, err) 445 | assert.EqualValues(t, TooFewPropertiesCode, err.Code()) 446 | // tooFewProperties = "%s in %s should have at least %d properties" 447 | assert.Equal(t, "path in body should have at least 10 properties", err.Error()) 448 | 449 | err = TooFewProperties("path", "", 10) 450 | require.Error(t, err) 451 | assert.EqualValues(t, TooFewPropertiesCode, err.Code()) 452 | // tooFewPropertiesNoIn = "%s should have at least %d properties" 453 | assert.Equal(t, "path should have at least 10 properties", err.Error()) 454 | }) 455 | 456 | t.Run("with PatternProperties", func(t *testing.T) { 457 | err := FailedAllPatternProperties("path", "body", "key") 458 | require.Error(t, err) 459 | assert.EqualValues(t, FailedAllPatternPropsCode, err.Code()) 460 | // failedAllPatternProps = "%s.%s in %s failed all pattern properties" 461 | assert.Equal(t, "path.key in body failed all pattern properties", err.Error()) 462 | 463 | err = FailedAllPatternProperties("path", "", "key") 464 | require.Error(t, err) 465 | assert.EqualValues(t, FailedAllPatternPropsCode, err.Code()) 466 | // failedAllPatternPropsNoIn = "%s.%s failed all pattern properties" 467 | assert.Equal(t, "path.key failed all pattern properties", err.Error()) 468 | }) 469 | } 470 | --------------------------------------------------------------------------------