diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index 3c305d7..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,107 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -This is a shared errors library for the go-openapi toolkit. It provides an `Error` interface and concrete error types for API errors and JSON-schema validation errors. The package is used throughout the go-openapi ecosystem (github.com/go-openapi). - -## Development Commands - -### Testing -```bash -# Run all tests -go test ./... - -# Run tests with coverage -go test -v -race -coverprofile=coverage.out ./... - -# Run a specific test -go test -v -run TestName ./... -``` - -### Linting -```bash -# Run golangci-lint (must be run before committing) -golangci-lint run -``` - -### Building -```bash -# Build the package -go build ./... - -# Verify dependencies -go mod verify -go mod tidy -``` - -## Architecture and Code Structure - -### Core Error Types - -The package provides a hierarchy of error types: - -1. **Error interface** (api.go:20-24): Base interface with `Code() int32` method that all errors implement -2. **apiError** (api.go:26-37): Simple error with code and message -3. **CompositeError** (schema.go:94-122): Groups multiple errors together, implements `Unwrap() []error` -4. **Validation** (headers.go:12-55): Represents validation failures with Name, In, Value fields -5. **ParseError** (parsing.go:12-42): Represents parsing errors with Reason field -6. **MethodNotAllowedError** (api.go:74-88): Special error for method not allowed with Allowed methods list -7. **APIVerificationFailed** (middleware.go:12-39): Error for API spec/registration mismatches - -### Error Categorization by File - -- **api.go**: Core error interface, basic error types, HTTP error serving -- **schema.go**: Validation errors (type, length, pattern, enum, min/max, uniqueness, properties) -- **headers.go**: Header validation errors (content-type, accept) -- **parsing.go**: Parameter parsing errors -- **auth.go**: Authentication errors -- **middleware.go**: API verification errors - -### Key Design Patterns - -1. **Error Codes**: Custom error codes >= 600 (maximumValidHTTPCode) to differentiate validation types without conflicting with HTTP status codes -2. **Conditional Messages**: Most constructors have "NoIn" variants for errors without an "In" field (e.g., tooLongMessage vs tooLongMessageNoIn) -3. **ServeError Function** (api.go:147-201): Central HTTP error handler using type assertions to handle different error types -4. **Flattening**: CompositeError flattens nested composite errors recursively (api.go:108-134) -5. **Name Validation**: Errors can have their Name field updated for nested properties via ValidateName methods - -### JSON Serialization - -All error types implement `MarshalJSON()` to provide structured JSON responses with code, message, and type-specific fields. - -## Testing Practices - -- Uses forked `github.com/go-openapi/testify/v2` for minimal test dependencies -- Tests follow pattern: `*_test.go` files next to implementation -- Test files cover: api_test.go, schema_test.go, middleware_test.go, parsing_test.go, auth_test.go - -## Code Quality Standards - -### Linting Configuration -- Enable all golangci-lint linters by default, with specific exclusions in .golangci.yml -- Complexity threshold: max 20 (cyclop, gocyclo) -- Line length: max 180 characters -- Run `golangci-lint run` before committing - -### Disabled Linters (and why) -Key exclusions from STYLE.md rationale: -- depguard: No import constraints enforced -- funlen: Function length not enforced (cognitive complexity preferred) -- godox: TODOs are acceptable -- nonamedreturns: Named returns are acceptable -- varnamelen: Short variable names allowed when appropriate - -## Release Process - -- Push semver tag (v{major}.{minor}.{patch}) to master branch -- CI automatically generates release with git-cliff -- Tags should be PGP-signed -- Tag message prepends release notes - -## Important Constants - -- `DefaultHTTPCode = 422` (http.StatusUnprocessableEntity) -- `maximumValidHTTPCode = 600` -- Custom error codes start at 600+ (InvalidTypeCode, RequiredFailCode, etc.) diff --git a/.cliff.toml b/.cliff.toml deleted file mode 100644 index 702629f..0000000 --- a/.cliff.toml +++ /dev/null @@ -1,181 +0,0 @@ -# git-cliff ~ configuration file -# https://git-cliff.org/docs/configuration - -[changelog] -header = """ -""" - -footer = """ - ------ - -**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms** - -[![License][license-badge]][license-url] - -[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg -[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme - -{%- macro remote_url() -%} - https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} -{%- endmacro -%} -""" - -body = """ -{%- if version %} -## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} -{%- else %} -## [unreleased] -{%- endif %} -{%- if message %} - {%- raw %}\n{% endraw %} -{{ message }} - {%- raw %}\n{% endraw %} -{%- endif %} -{%- if version %} - {%- if previous.version %} - -**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}> - {%- endif %} -{%- else %} - {%- raw %}\n{% endraw %} -{%- endif %} - -{%- if statistics %}{% if statistics.commit_count %} - {%- raw %}\n{% endraw %} -{{ statistics.commit_count }} commits in this release. - {%- raw %}\n{% endraw %} -{%- endif %}{% endif %} ------ - -{%- for group, commits in commits | group_by(attribute="group") %} - {%- raw %}\n{% endraw %} -### {{ group | upper_first }} - {%- raw %}\n{% endraw %} - {%- for commit in commits %} - {%- if commit.remote.pr_title %} - {%- set commit_message = commit.remote.pr_title %} - {%- else %} - {%- set commit_message = commit.message %} - {%- endif %} -* {{ commit_message | split(pat="\n") | first | trim }} - {%- if commit.remote.username %} -{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}) - {%- endif %} - {%- if commit.remote.pr_number %} -{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) - {%- endif %} -{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }}) - {%- endfor %} -{%- endfor %} - -{%- if github %} -{%- raw %}\n{% endraw -%} - {%- set all_contributors = github.contributors | length %} - {%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %} ------ - -### People who contributed to this release - {% endif %} - {%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %} - {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} -* [@{{ contributor.username }}](https://github.com/{{ contributor.username }}) - {%- endif %} - {%- endfor %} - - {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} ------ - {%- raw %}\n{% endraw %} - -### New Contributors - {%- endif %} - - {%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} - {%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %} -* @{{ contributor.username }} made their first contribution - {%- if contributor.pr_number %} - in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ - {%- endif %} - {%- endif %} - {%- endfor %} -{%- endif %} - -{%- raw %}\n{% endraw %} - -{%- macro remote_url() -%} - https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} -{%- endmacro -%} -""" -# Remove leading and trailing whitespaces from the changelog's body. -trim = true -# Render body even when there are no releases to process. -render_always = true -# An array of regex based postprocessors to modify the changelog. -postprocessors = [ - # Replace the placeholder with a URL. - #{ pattern = '', replace = "https://github.com/orhun/git-cliff" }, -] -# output file path -# output = "test.md" - -[git] -# Parse commits according to the conventional commits specification. -# See https://www.conventionalcommits.org -conventional_commits = false -# Exclude commits that do not match the conventional commits specification. -filter_unconventional = false -# Require all commits to be conventional. -# Takes precedence over filter_unconventional. -require_conventional = false -# Split commits on newlines, treating each line as an individual commit. -split_commits = false -# An array of regex based parsers to modify commit messages prior to further processing. -commit_preprocessors = [ - # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. - #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, - # Check spelling of the commit message using https://github.com/crate-ci/typos. - # If the spelling is incorrect, it will be fixed automatically. - #{ pattern = '.*', replace_command = 'typos --write-changes -' } -] -# Prevent commits that are breaking from being excluded by commit parsers. -protect_breaking_commits = false -# An array of regex based parsers for extracting data from the commit message. -# Assigns commits to groups. -# Optionally sets the commit's scope and can decide to exclude commits from further processing. -commit_parsers = [ - { message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true }, - { message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true }, - { field = "author.name", pattern = "dependabot*", group = "Updates" }, - { message = "([Ss]ecurity)|([Vv]uln)", group = "Security" }, - { body = "(.*[Ss]ecurity)|([Vv]uln)", group = "Security" }, - { message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "Code quality" }, - { message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "Documentation" }, - { message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "Implemented enhancements" }, - { message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "Miscellaneous tasks" }, - { message = "^test", group = "Testing" }, - { message = "(^fix)|(panic)", group = "Fixed bugs" }, - { message = "(^refact)|(rework)", group = "Refactor" }, - { message = "(^[Pp]erf)|(performance)", group = "Performance" }, - { message = "(^[Cc]hore)", group = "Miscellaneous tasks" }, - { message = "^[Rr]evert", group = "Reverted changes" }, - { message = "(upgrade.*?go)|(go\\s+version)", group = "Updates" }, - { message = ".*", group = "Other" }, -] -# Exclude commits that are not matched by any commit parser. -filter_commits = false -# An array of link parsers for extracting external references, and turning them into URLs, using regex. -link_parsers = [] -# Include only the tags that belong to the current branch. -use_branch_tags = false -# Order releases topologically instead of chronologically. -topo_order = false -# Order releases topologically instead of chronologically. -topo_order_commits = true -# Order of commits in each group/release within the changelog. -# Allowed values: newest, oldest -sort_commits = "newest" -# Process submodules commits -recurse_submodules = false - -#[remote.github] -#owner = "go-openapi" diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 85707f7..57c7cf5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,48 +1,57 @@ -## Contribution Guidelines +You'll find here general guidelines to contribute to this project. +They mostly correspond to standard practices for open source repositories. -You'll find below general guidelines, which mostly correspond to standard practices for open sourced repositories. +We have tried to keep things as simple as possible. ->**TL;DR** -> -> If you're already an experienced go developer on github, then you should just feel at home with us +> [!NOTE] +> If you're an experienced go developer on github, then you should just feel at home with us > and you may well skip the rest of this document. > -> You'll essentially find the usual guideline for a go library project on github. +> You'll essentially apply the usual guidelines for a go library project on github. + +These guidelines are common to all libraries published on github by the `go-openapi` organization, +so you'll feel at home with any of our projects. -These guidelines are general to all libraries published on github by the `go-openapi` organization. +You'll find more detailed (or repo-specific) instructions in the [maintainer's docs][maintainers-doc]. -You'll find more detailed (or repo-specific) instructions in the [maintainer's docs](../docs). +[maintainers-doc]: ../docs/MAINTAINERS.md -## How can I contribute? +## How can I contribute -There are many ways in which you can contribute. Here are a few ideas: +There are many ways in which you can contribute, not just code. Here are a few ideas: - * Reporting Issues / Bugs - * Suggesting Improvements - * Code - * bug fixes and new features that are within the main project scope - * improving test coverage - * addressing code quality issues - * Documentation - * Art work that makes the project look great +- Reporting issues or bugs +- Suggesting improvements +- Documentation +- Art work that makes the project look great +- Code + - proposing bug fixes and new features that are within the main project scope + - improving test coverage + - addressing code quality issues ## Questions & issues -### Asking questions +### Asking a question + +You may inquire anything about this library by reporting a "Question" issue on github. -You may inquire about anything about this library by reporting a "Question" issue on github. +You may also join our discord server where you may discuss issues or requests. + +[![Discord Server][discord-badge]][discord-url] + +[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue +[discord-url]: https://discord.gg/twZ9BwT3 ### Reporting issues Reporting a problem with our libraries _is_ a valuable contribution. - You can do this on the github issues page of this repository. Please be as specific as possible when describing your issue. Whenever relevant, please provide information about your environment (go version, OS). -Adding a code snippet to reproduce the issue is great, and as a big time saver for maintainers. +Adding a code snippet to reproduce the issue is great, and a big time saver for maintainers. ### Triaging issues @@ -62,14 +71,16 @@ process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it. If your pull request is not accepted on the first try, don't be discouraged! -If there's a problem with the implementation, hopefully you received feedback on what to improve. +If there's a problem with the implementation, hopefully you've received feedback on what to improve. If you have a lot of ideas or a lot of issues to solve, try to refrain a bit and post focused pull requests. -Think that they must be reviewed by a maintainer and it is easy to lost track of things on big PRs. +Think that they must be reviewed by a maintainer and it is easy to lose track of things on big PRs. We're trying very hard to keep the go-openapi packages lean and focused. -These packages constitute a toolkit: it won't do everything for everybody out of the box, + +Together, these packages constitute a toolkit for go developers: +it won't do everything for everybody out of the box, but everybody can use it to do just about everything related to OpenAPI. This means that we might decide against incorporating a new feature. @@ -80,9 +91,11 @@ However, there might be a way to implement that feature *on top of* our librarie You just need a `go` compiler to be installed. No special tools are needed to work with our libraries. -The go compiler version required is always the old stable (latest minor go version - 1). +The minimal go compiler version required is always the old stable (latest minor go version - 1). + +Our libraries are designed and tested to work on `Linux`, `MacOS` and `Windows`. -If you're already used to work with `go` you should already have everything in place. +If you're used to work with `go` you should already have everything in place. Although not required, you'll be certainly more productive with a local installation of `golangci-lint`, the meta-linter our CI uses. @@ -104,12 +117,12 @@ github will propose to open a pull request on the original repository. Typically you'd follow some common naming conventions: -- if it's a bugfix branch, name it `fix/XXX-something`where XXX is the number of the +- if it's a bug fixing branch, name it `fix/XXX-something` where XXX is the number of the issue on github - if it's a feature branch, create an enhancement issue to announce your intentions, and name it `feature/XXX-something` where XXX is the number of the issue. -> NOTE: we don't enforce naming conventions on branches: it's your fork after all. +NOTE: we don't enforce naming conventions on branches: it's your fork after all. #### Tests @@ -121,10 +134,10 @@ Take a look at existing tests for inspiration, and run the full test suite on yo before submitting a pull request. Our CI measures test coverage and the test coverage of every patch. + Although not a blocking step - because there are so many special cases - this is an indicator that maintainers consider when approving a PR. - -Please try your best to cover about 80% of your patch. +Please try your best to cover at least 80% of your patch. #### Code style @@ -132,13 +145,13 @@ You may read our stance on code style [there](../docs/STYLE.md). #### Documentation -Don't forget to update the documentation when creating or modifying features. +Don't forget to update the documentation when creating or modifying a feature. Most documentation for this library is directly found in code as comments for godoc. -The documentation for the go-openapi packages is published on the public go docs site: +The documentation for this go-openapi package is published on [the public go docs site][go-doc]. - +--- Check your documentation changes for clarity, concision, and correctness. @@ -150,11 +163,14 @@ go install golang.org/x/pkgsite/cmd/pkgsite@latest ``` Then run on the repository folder: + ```sh pkgsite . ``` -This wil run a godoc server locally where you may see the documentation generated from your local repository. +This will run a godoc server locally where you may see the documentation generated from your local repository. + +[go-doc]: https://pkg.go.dev/github.com/go-openapi/errors #### Commit messages @@ -164,7 +180,7 @@ reference to all the issues that they address. Pull requests must not contain commits from other users or branches. Commit messages are not required to follow the "conventional commit" rule, but it's certainly a good -thing to follow this guidelinea (e.g. "fix: blah blah", "ci: did this", "feat: did that" ...). +thing to follow that convention (e.g. "fix: fixed panic in XYZ", "ci: did this", "feat: did that" ...). The title in your commit message is used directly to produce our release notes: try to keep them neat. @@ -186,7 +202,7 @@ Be sure to post a comment after pushing. The new commits will show up in the pul request automatically, but the reviewers will not be notified unless you comment. Before the pull request is merged, -**make sure that you squash your commits into logical units of work** +**make sure that you've squashed your commits into logical units of work** using `git rebase -i` and `git push -f`. After every commit the test suite should be passing. @@ -195,6 +211,8 @@ Include documentation changes in the same commit so that a revert would remove a #### Sign your work +Software is developed by real people. + The sign-off is a simple line at the end of your commit message, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. @@ -204,11 +222,30 @@ PGP-signed commit are greatly appreciated but not required. The rules are pretty simple: -* read our [DCO](./DCO.md) (from [developercertificate.org](http://developercertificate.org/)) -* if you agree with these terms, then you just add a line to every git commit message +- read our [DCO][dco-doc] (from [developercertificate.org][dco-source]) +- if you agree with these terms, then you just add a line to every git commit message - Signed-off-by: Joe Smith +``` +Signed-off-by: Joe Smith +``` using your real name (sorry, no pseudonyms or anonymous contributions.) -You can add the sign off when creating the git commit via `git commit -s`. +You can add the sign-off when creating the git commit via `git commit -s`. + +[dco-doc]: ./DCO.md +[dco-source]: https://developercertificate.org + +## Code contributions by AI agents + +Our agentic friends are welcome to contribute! + +We only have a few demands to keep-up with human maintainers. + +1. Issues and PRs written or posted by agents should always mention the original (human) poster for reference +2. We don't accept PRs attributed to agents. We don't want commits signed like "author: @claude.code". + Agents or bots may coauthor commits, though. +3. Security vulnerability reports by agents should always be reported privately and mention the original (human) poster + (see also [Security Policy][security-doc]). + +[security-doc]: ../SECURITY.md diff --git a/.github/DCO.md b/.github/DCO.md index e168dc4..78a2d64 100644 --- a/.github/DCO.md +++ b/.github/DCO.md @@ -1,4 +1,4 @@ - # Developer's Certificate of Origin +# Developer's Certificate of Origin ``` Developer Certificate of Origin diff --git a/.github/wordlist.txt b/.github/wordlist.txt new file mode 100644 index 0000000..550941d --- /dev/null +++ b/.github/wordlist.txt @@ -0,0 +1,48 @@ +CodeFactor +CodeQL +DCO +ECMA +GoDoc +JSON +Maintainer's +PR's +PRs +Repo +SPDX +TODOs +Triaging +UI +Unprocessable +XYZ +acronym +agentic +api +ci +codebase +codecov +config +dependabot +dev +developercertificate +enum +fka +github +godoc +golang +golangci +https +jsonpointer +linter's +linters +maintainer's +md +metalinter +middleware +monorepo +multipleOf +openapi +prepended +repos +semver +sexualized +vuln diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 9b81e3a..48e5a57 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -11,5 +11,5 @@ jobs: permissions: contents: write pull-requests: write - uses: go-openapi/ci-workflows/.github/workflows/auto-merge.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # v0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/auto-merge.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 secrets: inherit diff --git a/.github/workflows/bump-release.yml b/.github/workflows/bump-release.yml index 8d209a6..57f7f75 100644 --- a/.github/workflows/bump-release.yml +++ b/.github/workflows/bump-release.yml @@ -36,7 +36,7 @@ jobs: bump-release: permissions: contents: write - uses: go-openapi/ci-workflows/.github/workflows/bump-release.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # v0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/bump-release.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 with: bump-patch: ${{ inputs.bump-patch }} bump-minor: ${{ inputs.bump-minor }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e503cb0..32fb5e3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -18,5 +18,5 @@ jobs: permissions: contents: read security-events: write - uses: go-openapi/ci-workflows/.github/workflows/codeql.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # v0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/codeql.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 secrets: inherit diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index c7fcf5b..33a469f 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -14,5 +14,5 @@ jobs: permissions: pull-requests: write contents: write - uses: go-openapi/ci-workflows/.github/workflows/contributors.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # v0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/contributors.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 secrets: inherit diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index e1ba8d8..514d891 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -13,5 +13,5 @@ on: jobs: test: - uses: go-openapi/ci-workflows/.github/workflows/go-test.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # v0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/go-test.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 secrets: inherit diff --git a/.github/workflows/scanner.yml b/.github/workflows/scanner.yml index ed357e2..2bff760 100644 --- a/.github/workflows/scanner.yml +++ b/.github/workflows/scanner.yml @@ -15,5 +15,5 @@ jobs: permissions: contents: read security-events: write - uses: go-openapi/ci-workflows/.github/workflows/scanner.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # V0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/scanner.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 secrets: inherit diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 3c43012..b834743 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -13,7 +13,7 @@ jobs: name: Create release permissions: contents: write - uses: go-openapi/ci-workflows/.github/workflows/release.yml@84f8f9c0759d5d1d0c32b18a7abaa0cba65ebcff # v0.2.9 + uses: go-openapi/ci-workflows/.github/workflows/release.yml@435746a4b72b06b6b6989c309fd2ad8150dbae5a # v0.2.11 with: tag: ${{ github.ref_name }} secrets: inherit diff --git a/.gitignore b/.gitignore index 9a8da7e..9364443 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ -secrets.yml *.out +*.cov +.idea +.env +.mcp.json +.claude/ settings.local.json diff --git a/.golangci.yml b/.golangci.yml index fdae591..e2c14be 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,6 +12,7 @@ linters: - paralleltest - recvcheck - testpackage + - thelper - tparallel - varnamelen - whitespace @@ -40,6 +41,10 @@ linters: - common-false-positives - legacy - std-error-handling + rules: + - linters: + - revive + text: "avoid package names that conflict with Go standard library package names" paths: - third_party$ - builtin$ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 9322b06..bac878f 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -23,7 +23,9 @@ include: Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or + advances + * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic @@ -55,7 +57,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at ivan+abuse@flanders.co.nz. All +reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. @@ -68,7 +70,7 @@ members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] +available at [][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 3cdf8fd..d49e377 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -8,18 +8,18 @@ | Username | All Time Contribution Count | All Commits | | --- | --- | --- | -| @casualjim | 58 | https://github.com/go-openapi/errors/commits?author=casualjim | -| @fredbi | 36 | https://github.com/go-openapi/errors/commits?author=fredbi | -| @youyuanwu | 5 | https://github.com/go-openapi/errors/commits?author=youyuanwu | -| @alexandear | 2 | https://github.com/go-openapi/errors/commits?author=alexandear | -| @fiorix | 1 | https://github.com/go-openapi/errors/commits?author=fiorix | -| @ligustah | 1 | https://github.com/go-openapi/errors/commits?author=ligustah | -| @artemseleznev | 1 | https://github.com/go-openapi/errors/commits?author=artemseleznev | -| @gautierdelorme | 1 | https://github.com/go-openapi/errors/commits?author=gautierdelorme | -| @guillemj | 1 | https://github.com/go-openapi/errors/commits?author=guillemj | -| @maxatome | 1 | https://github.com/go-openapi/errors/commits?author=maxatome | -| @Simon-Li | 1 | https://github.com/go-openapi/errors/commits?author=Simon-Li | -| @aokumasan | 1 | https://github.com/go-openapi/errors/commits?author=aokumasan | -| @ujjwalsh | 1 | https://github.com/go-openapi/errors/commits?author=ujjwalsh | +| @casualjim | 58 | | +| @fredbi | 36 | | +| @youyuanwu | 5 | | +| @alexandear | 2 | | +| @fiorix | 1 | | +| @ligustah | 1 | | +| @artemseleznev | 1 | | +| @gautierdelorme | 1 | | +| @guillemj | 1 | | +| @maxatome | 1 | | +| @Simon-Li | 1 | | +| @aokumasan | 1 | | +| @ujjwalsh | 1 | | _this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_ diff --git a/README.md b/README.md index 6102c6b..d9f4a3f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,9 @@ errNotImplemented := NotImplemented("method: %s", url) See ## Licensing @@ -59,12 +61,9 @@ See This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE). - ## Other documentation @@ -95,23 +94,19 @@ Maintainers can cut a new release by either: [release-badge]: https://badge.fury.io/gh/go-openapi%2Ferrors.svg [release-url]: https://badge.fury.io/gh/go-openapi%2Ferrors -[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Ferrors.svg -[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Ferrors [gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/errors [gocard-url]: https://goreportcard.com/report/github.com/go-openapi/errors [codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/errors [codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/errors -[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2Fgo-openapi%2F -[doc-url]: https://goswagger.io/go-openapi [godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/errors [godoc-url]: http://pkg.go.dev/github.com/go-openapi/errors [slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png [slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM [slack-url]: https://goswagger.slack.com/archives/C04R30YMU [discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue -[discord-url]: https://discord.gg/DrafRmZx +[discord-url]: https://discord.gg/twZ9BwT3 [license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg diff --git a/SECURITY.md b/SECURITY.md index 2a7b6f0..6ceb159 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,14 +6,32 @@ This policy outlines the commitment and practices of the go-openapi maintainers | Version | Supported | | ------- | ------------------ | -| 0.22.x | :white_check_mark: | +| 0.x | :white_check_mark: | + +## Vulnerability checks in place + +This repository uses automated vulnerability scans, at every merged commit and at least once a week. + +We use: + +* [`GitHub CodeQL`][codeql-url] +* [`trivy`][trivy-url] +* [`govulncheck`][govulncheck-url] + +Reports are centralized in github security reports and visible only to the maintainers. ## Reporting a vulnerability If you become aware of a security vulnerability that affects the current repository, -please report it privately to the maintainers. +**please report it privately to the maintainers** +rather than opening a publicly visible GitHub issue. + +Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url]. -Please follow the instructions provided by github to -[Privately report a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability). +> [!NOTE] +> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability". -TL;DR: on Github, navigate to the project's "Security" tab then click on "Report a vulnerability". +[codeql-url]: https://github.com/github/codeql +[trivy-url]: https://trivy.dev/docs/latest/getting-started +[govulncheck-url]: https://go.dev/blog/govulncheck +[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability diff --git a/api.go b/api.go index cb13941..d2b4427 100644 --- a/api.go +++ b/api.go @@ -146,7 +146,7 @@ func MethodNotAllowed(requested string, allow []string) Error { } } -// ServeError implements the http error handler interface. +// ServeError implements the [http] error handler interface. func ServeError(rw http.ResponseWriter, r *http.Request, err error) { rw.Header().Set("Content-Type", "application/json") diff --git a/api_test.go b/api_test.go index 272ba1e..8b36440 100644 --- a/api_test.go +++ b/api_test.go @@ -28,10 +28,10 @@ func TestServeError(t *testing.T) { recorder := httptest.NewRecorder() ServeError(recorder, nil, err) - assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code) - assert.Equal(t, "POST,PUT", recorder.Header().Get("Allow")) + assert.EqualT(t, http.StatusMethodNotAllowed, recorder.Code) + assert.EqualT(t, "POST,PUT", recorder.Header().Get("Allow")) // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) - assert.JSONEq(t, + assert.JSONEqT(t, `{"code":405,"message":"method GET is not allowed, but [POST,PUT] are"}`, recorder.Body.String(), ) @@ -43,9 +43,9 @@ func TestServeError(t *testing.T) { recorder := httptest.NewRecorder() ServeError(recorder, nil, err) - assert.Equal(t, http.StatusNotFound, recorder.Code) + assert.EqualT(t, http.StatusNotFound, recorder.Code) // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) - assert.JSONEq(t, + assert.JSONEqT(t, `{"code":404,"message":"Not found"}`, recorder.Body.String(), ) @@ -58,9 +58,9 @@ func TestServeError(t *testing.T) { recorder := httptest.NewRecorder() ServeError(recorder, nil, err) - assert.Equal(t, http.StatusUnprocessableEntity, recorder.Code) + assert.EqualT(t, http.StatusUnprocessableEntity, recorder.Code) // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) - assert.JSONEq(t, + assert.JSONEqT(t, `{"code":601,"message":"someType is an invalid type name"}`, recorder.Body.String(), ) @@ -77,9 +77,9 @@ func TestServeError(t *testing.T) { recorder := httptest.NewRecorder() ServeError(recorder, nil, err) - assert.Equal(t, http.StatusBadRequest, recorder.Code) + assert.EqualT(t, http.StatusBadRequest, recorder.Code) // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) - assert.JSONEq(t, + assert.JSONEqT(t, `{"code":601,"message":"someType is an invalid type name"}`, recorder.Body.String(), ) @@ -90,9 +90,9 @@ func TestServeError(t *testing.T) { simpleErr := errors.New("some error") recorder := httptest.NewRecorder() ServeError(recorder, nil, simpleErr) - assert.Equal(t, http.StatusInternalServerError, recorder.Code) + assert.EqualT(t, http.StatusInternalServerError, recorder.Code) // assert.Equal(t, "application/json", recorder.Header().Get("content-type")) - assert.JSONEq(t, + assert.JSONEqT(t, `{"code":500,"message":"some error"}`, recorder.Body.String(), ) @@ -108,8 +108,8 @@ func TestServeError(t *testing.T) { } recorder := httptest.NewRecorder() ServeError(recorder, nil, compositeErr) - assert.Equal(t, http.StatusInternalServerError, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, http.StatusInternalServerError, recorder.Code) + assert.JSONEqT(t, `{"code":500,"message":"firstError"}`, recorder.Body.String(), ) @@ -124,8 +124,8 @@ func TestServeError(t *testing.T) { } recorder := httptest.NewRecorder() ServeError(recorder, nil, compositeErr) - assert.Equal(t, CompositeErrorCode, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, CompositeErrorCode, recorder.Code) + assert.JSONEqT(t, `{"code":600,"message":"myApiError"}`, recorder.Body.String(), ) @@ -144,8 +144,8 @@ func TestServeError(t *testing.T) { } recorder := httptest.NewRecorder() ServeError(recorder, nil, compositeErr) - assert.Equal(t, CompositeErrorCode, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, CompositeErrorCode, recorder.Code) + assert.JSONEqT(t, `{"code":600,"message":"myApiError"}`, recorder.Body.String(), ) @@ -162,8 +162,8 @@ func TestServeError(t *testing.T) { } recorder := httptest.NewRecorder() ServeError(recorder, nil, compositeErr) - assert.Equal(t, http.StatusInternalServerError, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, http.StatusInternalServerError, recorder.Code) + assert.JSONEqT(t, `{"code":500,"message":"Unknown error"}`, recorder.Body.String(), ) @@ -184,8 +184,8 @@ func TestServeError(t *testing.T) { recorder := httptest.NewRecorder() ServeError(recorder, nil, compositeErr) - assert.Equal(t, CompositeErrorCode, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, CompositeErrorCode, recorder.Code) + assert.JSONEqT(t, `{"code":600,"message":"myApiError"}`, recorder.Body.String(), ) @@ -194,8 +194,8 @@ func TestServeError(t *testing.T) { t.Run("check guard against nil type", func(t *testing.T) { recorder := httptest.NewRecorder() ServeError(recorder, nil, nil) - assert.Equal(t, http.StatusInternalServerError, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, http.StatusInternalServerError, recorder.Code) + assert.JSONEqT(t, `{"code":500,"message":"Unknown error"}`, recorder.Body.String(), ) @@ -205,8 +205,8 @@ func TestServeError(t *testing.T) { recorder := httptest.NewRecorder() var z *customError ServeError(recorder, nil, z) - assert.Equal(t, http.StatusInternalServerError, recorder.Code) - assert.JSONEq(t, + assert.EqualT(t, http.StatusInternalServerError, recorder.Code) + assert.JSONEqT(t, `{"code":500,"message":"Unknown error"}`, recorder.Body.String(), ) @@ -218,37 +218,37 @@ func TestAPIErrors(t *testing.T) { err := New(402, "this failed %s", "yada") require.Error(t, err) assert.EqualValues(t, 402, err.Code()) - assert.Equal(t, "this failed yada", err.Error()) + assert.EqualT(t, "this failed yada", err.Error()) err = NotFound("this failed %d", 1) require.Error(t, err) assert.EqualValues(t, http.StatusNotFound, err.Code()) - assert.Equal(t, "this failed 1", err.Error()) + assert.EqualT(t, "this failed 1", err.Error()) err = NotFound("") require.Error(t, err) assert.EqualValues(t, http.StatusNotFound, err.Code()) - assert.Equal(t, "Not found", err.Error()) + assert.EqualT(t, "Not found", err.Error()) err = NotImplemented("not implemented") require.Error(t, err) assert.EqualValues(t, http.StatusNotImplemented, err.Code()) - assert.Equal(t, "not implemented", err.Error()) + assert.EqualT(t, "not implemented", err.Error()) err = MethodNotAllowed("GET", []string{"POST", "PUT"}) require.Error(t, err) assert.EqualValues(t, http.StatusMethodNotAllowed, err.Code()) - assert.Equal(t, "method GET is not allowed, but [POST,PUT] are", err.Error()) + assert.EqualT(t, "method GET is not allowed, but [POST,PUT] are", err.Error()) err = InvalidContentType("application/saml", []string{"application/json", "application/x-yaml"}) require.Error(t, err) assert.EqualValues(t, http.StatusUnsupportedMediaType, err.Code()) - assert.Equal(t, "unsupported media type \"application/saml\", only [application/json application/x-yaml] are allowed", err.Error()) + assert.EqualT(t, "unsupported media type \"application/saml\", only [application/json application/x-yaml] are allowed", err.Error()) err = InvalidResponseFormat("application/saml", []string{"application/json", "application/x-yaml"}) require.Error(t, err) assert.EqualValues(t, http.StatusNotAcceptable, err.Code()) - assert.Equal(t, "unsupported media type requested, only [application/json application/x-yaml] are available", err.Error()) + assert.EqualT(t, "unsupported media type requested, only [application/json application/x-yaml] are available", err.Error()) } func TestValidateName(t *testing.T) { @@ -256,13 +256,13 @@ func TestValidateName(t *testing.T) { // unchanged vv := v.ValidateName("") - assert.Equal(t, "myValidation", vv.Name) - assert.Equal(t, "myMessage", vv.message) + assert.EqualT(t, "myValidation", vv.Name) + assert.EqualT(t, "myMessage", vv.message) // forced vv = v.ValidateName("myNewName") - assert.Equal(t, "myNewName.myValidation", vv.Name) - assert.Equal(t, "myNewName.myMessage", vv.message) + assert.EqualT(t, "myNewName.myValidation", vv.Name) + assert.EqualT(t, "myNewName.myMessage", vv.message) v.Name = "" v.message = "myMessage" @@ -270,12 +270,12 @@ func TestValidateName(t *testing.T) { // unchanged vv = v.ValidateName("") assert.Empty(t, vv.Name) - assert.Equal(t, "myMessage", vv.message) + assert.EqualT(t, "myMessage", vv.message) // forced vv = v.ValidateName("myNewName") - assert.Equal(t, "myNewName", vv.Name) - assert.Equal(t, "myNewNamemyMessage", vv.message) + assert.EqualT(t, "myNewName", vv.Name) + assert.EqualT(t, "myNewNamemyMessage", vv.message) } func TestMarshalJSON(t *testing.T) { @@ -296,25 +296,25 @@ func TestMarshalJSON(t *testing.T) { `{"code":%d,"message":"%s","name":"Content-Type","in":"header","value":"%s","values":["a","b"]}`, expectedCode, expectedMessage, value, ) - assert.JSONEq(t, expectedJSON, string(jazon)) + assert.JSONEqT(t, expectedJSON, string(jazon)) a := apiError{code: 1, message: "a"} jazon, err = a.MarshalJSON() require.NoError(t, err) - assert.JSONEq(t, `{"code":1,"message":"a"}`, string(jazon)) + assert.JSONEqT(t, `{"code":1,"message":"a"}`, string(jazon)) m := MethodNotAllowedError{code: 1, message: "a", Allowed: []string{"POST"}} jazon, err = m.MarshalJSON() require.NoError(t, err) - assert.JSONEq(t, `{"code":1,"message":"a","allowed":["POST"]}`, string(jazon)) + assert.JSONEqT(t, `{"code":1,"message":"a","allowed":["POST"]}`, string(jazon)) c := CompositeError{Errors: []error{e}, code: 1, message: "a"} jazon, err = c.MarshalJSON() require.NoError(t, err) - assert.JSONEq(t, fmt.Sprintf(`{"code":1,"message":"a","errors":[%s]}`, expectedJSON), string(jazon)) + assert.JSONEqT(t, fmt.Sprintf(`{"code":1,"message":"a","errors":[%s]}`, expectedJSON), string(jazon)) p := ParseError{code: 1, message: "x", Name: "a", In: "b", Value: "c", Reason: errors.New("d")} jazon, err = p.MarshalJSON() require.NoError(t, err) - assert.JSONEq(t, `{"code":1,"message":"x","name":"a","in":"b","value":"c","reason":"d"}`, string(jazon)) + assert.JSONEqT(t, `{"code":1,"message":"x","name":"a","in":"b","value":"c","reason":"d"}`, string(jazon)) } diff --git a/auth_test.go b/auth_test.go index 24c0200..0cf3766 100644 --- a/auth_test.go +++ b/auth_test.go @@ -12,5 +12,5 @@ import ( func TestUnauthenticated(t *testing.T) { err := Unauthenticated("basic") assert.EqualValues(t, 401, err.Code()) - assert.Equal(t, "unauthenticated for basic", err.Error()) + assert.EqualT(t, "unauthenticated for basic", err.Error()) } diff --git a/doc.go b/doc.go index b4627f3..208c740 100644 --- a/doc.go +++ b/doc.go @@ -1,15 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers // SPDX-License-Identifier: Apache-2.0 -/* -Package errors provides an Error interface and several concrete types -implementing this interface to manage API errors and JSON-schema validation -errors. - -A middleware handler ServeError() is provided to serve the errors types -it defines. - -It is used throughout the various go-openapi toolkit libraries -(https://github.com/go-openapi). -*/ +// Package errors provides an Error interface and several concrete types +// implementing this interface to manage API errors and JSON-schema validation +// errors. +// +// A middleware handler [ServeError]() is provided to serve the errors types +// it defines. +// +// It is used throughout the various go-openapi toolkit libraries. +// (https://github.com/go-openapi). package errors diff --git a/docs/MAINTAINERS.md b/docs/MAINTAINERS.md index b55cd77..01f43a8 100644 --- a/docs/MAINTAINERS.md +++ b/docs/MAINTAINERS.md @@ -1,37 +1,33 @@ -# Maintainer's guide +> [!NOTE] +> Comprehensive guide for maintainers covering repository structure, CI/CD workflows, release procedures, and development practices. +> Essential reading for anyone contributing to or maintaining this project. ## Repo structure -Single go module. - -> **NOTE** -> -> Some `go-openapi` repos are mono-repos with multiple modules, -> with adapted CI workflows. +This project is organized as a repo with a single go module. ## Repo configuration -* default branch: master -* protected branches: master -* branch protection rules: +* Default branch: master +* Protected branches: master +* Branch protection rules: * require pull requests and approval - * required status checks: - - DCO (simple email sign-off) - - Lint - - tests completed -* auto-merge enabled (used for dependabot updates) + * required status checks: + * DCO (simple email sign-off) + * Lint + * All tests completed +* Auto-merge enabled (used for dependabot updates and other auto-merged PR's, e.g. contributors update) ## Continuous Integration ### Code Quality checks -* meta-linter: golangci-lint -* linter config: [`.golangci.yml`](../.golangci.yml) (see our [posture](./STYLE.md) on linters) - -* Code quality assessment: [CodeFactor](https://www.codefactor.io/dashboard) +* meta-linter: [golangci-lint][golangci-url] +* linter config: [`.golangci.yml`][linter-config] (see our [posture][style-doc] on linters) +* Code quality assessment: [CodeFactor][codefactor-url] * Code quality badges - * go report card: - * CodeFactor: + * [go report card][gocard-url] + * [CodeFactor][codefactor-url] > **NOTES** > @@ -58,7 +54,7 @@ Coverage threshold status is informative and not blocking. This is because the thresholds are difficult to tune and codecov oftentimes reports false negatives or may fail to upload coverage. -All tests use our fork of `stretchr/testify`: `github.com/go-openapi/testify`. +All tests across `go-openapi` use our fork of `stretchr/errors` (this repo): `github.com/go-openapi/errors`. This allows for minimal test dependencies. > **NOTES** @@ -76,7 +72,7 @@ This allows for minimal test dependencies. ### Automated updates * dependabot - * configuration: [`dependabot.yaml`](../.github/dependabot.yaml) + * configuration: [`dependabot.yaml`][dependabot-config] Principle: @@ -84,7 +80,7 @@ This allows for minimal test dependencies. * all updates from "trusted" dependencies (github actions, golang.org packages, go-openapi packages are auto-merged if they successfully pass CI. -* go version udpates +* go version updates Principle: @@ -92,8 +88,14 @@ This allows for minimal test dependencies. * `go.mod` should be updated (manually) whenever there is a new go minor release (e.g. every 6 months). + > This means that our projects always have a 6 months lag to enforce new features from the go compiler. + > + > However, new features of go may be used with a "go:build" tag: this allows users of the newer + > version to benefit the new feature while users still running with `oldstable` use another version + > that still builds. + * contributors - * a [`CONTRIBUTORS.md`](../CONTRIBUTORS.md) file is updated weekly, with all-time contributors to the repository + * a [`CONTRIBUTORS.md`][contributors-doc] file is updated weekly, with all-time contributors to the repository * the `github-actions[bot]` posts a pull request to do that automatically * at this moment, this pull request is not auto-approved/auto-merged (bot cannot approve its own PRs) @@ -101,7 +103,7 @@ This allows for minimal test dependencies. There are 3 complementary scanners - obviously, there is some overlap, but each has a different focus. -* github `CodeQL` +* GitHub `CodeQL` * `trivy` * `govulnscan` @@ -115,45 +117,68 @@ Reports are centralized in github security reports for code scanning tools. ## Releases +**For single module repos:** + +A bump release workflow can be triggered from the github actions UI to cut a release with a few clicks. + The release process is minimalist: * push a semver tag (i.e v{major}.{minor}.{patch}) to the master branch. * the CI handles this to generate a github release with release notes * release notes generator: git-cliff -* configuration: [`cliff.toml`](../.cliff.toml) +* configuration: the `.cliff.toml` is defined as a share configuration on + remote repo [`ci-workflows/.cliff.toml`][remote-cliff-config] + +Commits from maintainers are preferably PGP-signed. Tags are preferably PGP-signed. +We want our releases to show as "verified" on github. + The tag message introduces the release notes (e.g. a summary of this release). The release notes generator does not assume that commits are necessarily "conventional commits". -## Other files +**For mono-repos with multiple modules:** -Standard documentation: +The release process is slightly different because we need to update cross-module dependencies +before pushing a tag. -* [`CONTRIBUTING.md`](../.github/CONTRIBUTING.md) guidelines -* [`DCO.md`](../.github/DCO.md) terms for first-time contributors to read -* [`CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md) -* [`SECURIY.md`](../SECURITY.md) policy: how to report vulnerabilities privately -* [`LICENSE`](../LICENSE) terms - +A bump release workflow (mono-repo) can be triggered from the github actions UI to cut a release with a few clicks. -Reference documentation (released): +It works with the same input as the one for single module repos, and first creates a PR (auto-merged) +that updates the different go.mod files _before_ pushing the desired git tag. + +Commits and tags pushed by the workflow bot are PGP-signed ("go-openapi[bot]"). -* [godoc](https://pkg.go.dev/github.com/go-openapi/errors) +## Other files + +Standard documentation: -## TODOs & other ideas +* [CONTRIBUTING.md][contributing-doc] guidelines +* [DCO.md][dco-doc] terms for first-time contributors to read +* [CODE_OF_CONDUCT.md][coc-doc] +* [SECURITY.md][security-doc] policy: how to report vulnerabilities privately +* [LICENSE][license-doc] terms + -A few things remain ahead to ease a bit a maintainer's job: +Reference documentation (released): -* [x] reuse CI workflows (e.g. in `github.com/go-openapi/workflows`) -* [x] reusable actions with custom tools pinned (e.g. in `github.com/go-openapi/gh-actions`) -* [ ] open-source license checks -* [x] auto-merge for CONTRIBUTORS.md (requires a github app to produce tokens) -* [ ] more automated code renovation / relinting work (possibly built with CLAUDE) -* [ ] organization-level documentation web site -* ... +* [pkg.go.dev (fka godoc)][godoc-url] + + +[linter-config]: https://github.com/go-openapi/errors/blob/master/.golangci.yml +[remote-cliff-config]: https://github.com/go-openapi/ci-workflows/blob/master/.cliff.toml +[dependabot-config]: https://github.com/go-openapi/errors/blob/master/.github/dependabot.yaml +[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/errors +[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/errors +[golangci-url]: https://golangci-lint.run/ +[godoc-url]: https://pkg.go.dev/github.com/go-openapi/errors +[contributors-doc]: ../CONTRIBUTORS.md +[contributing-doc]: ../.github/CONTRIBUTING.md +[dco-doc]: ../.github/DCO.md +[style-doc]: STYLE.md +[coc-doc]: ../CODE_OF_CONDUCT.md +[security-doc]: ../SECURITY.md +[license-doc]: ../LICENSE diff --git a/docs/STYLE.md b/docs/STYLE.md index a82dad4..81c9696 100644 --- a/docs/STYLE.md +++ b/docs/STYLE.md @@ -2,14 +2,14 @@ > **TL;DR** > -> Let's be honest: at `go-openapi` and `go-swagger` we've never been super-strict on code style etc. +> Let's be honest: at `go-openapi` and `go-swagger` we've never been super-strict on code style and linting. > > But perhaps now (2025) is the time to adopt a different stance. Even though our repos have been early adopters of `golangci-lint` years ago (we used some other metalinter before), our decade-old codebase is only realigned to new rules from time to time. -Now go-openapi and go-swagger make up a really large codebase, which is taxing to maintain and keep afloat. +Now go-openapi and go-swagger together make up a really large codebase, which is taxing to maintain and keep afloat. Code quality and the harmonization of rules have thus become things that we need now. @@ -21,8 +21,13 @@ You should run `golangci-lint run` before committing your changes. Many editors have plugins that do that automatically. -> We use the `golangci-lint` meta-linter. The configuration lies in [`.golangci.yml`](../.golangci.yml). -> You may read for additional reference. +> We use the `golangci-lint` meta-linter. The configuration lies in +> [`.golangci.yml`][golangci-yml]. +> You may read [the linter's configuration reference][golangci-doc] for additional reference. + +This configuration is essentially the same across all `go-openapi` projects. + +Some projects may require slightly different settings. ## Linting rules posture @@ -30,9 +35,32 @@ Thanks to go's original design, we developers don't have to waste much time argu However, the number of available linters has been growing to the point that we need to pick a choice. +### Our approach: evaluate, don't consume blindly + +As early adopters of `golangci-lint` (and its predecessors), we've watched linting orthodoxy +shift back and forth over the years. Patterns that were idiomatic one year get flagged the next; +rules that seemed reasonable in isolation produce noise at scale. Conversations with maintainers +of other large Go projects confirmed what our own experience taught us: +**the default linter set is a starting point, not a prescription**. + +Our stance is deliberate: + +- **Start from `default: all`**, then consciously disable what doesn't earn its keep. + This forces us to evaluate every linter and articulate why we reject it — the disabled list + is a design rationale, not technical debt. +- **Tune thresholds rather than disable** when a linter's principle is sound but its defaults + are too aggressive for a mature codebase. +- **Require justification for every `//nolint`** directive. Each one must carry an inline comment + explaining why it's there. +- **Prefer disabling a linter over scattering `//nolint`** across the codebase. If a linter + produces systematic false positives on patterns we use intentionally, the linter goes — + not our code. +- **Keep the configuration consistent** across all `go-openapi` repositories. Per-repo + divergence is a maintenance tax we don't want to pay. + We enable all linters published by `golangci-lint` by default, then disable a few ones. -Here are the reasons why they are disabled (update: Nov. 2025, `golangci-lint v2.6.1`): +Here are the reasons why they are disabled (update: Feb. 2026, `golangci-lint v2.8.0`). ```yaml disable: @@ -46,6 +74,7 @@ Here are the reasons why they are disabled (update: Nov. 2025, `golangci-lint v2 - paralleltest # we like parallel tests. We just don't want them to be enforced everywhere - recvcheck # we like the idea of having pointer and non-pointer receivers - testpackage # we like test packages. We just don't want them to be enforced everywhere + - thelper # too many false positives on test case factories returning func(*testing.T). See note below - tparallel # see paralleltest - varnamelen # sometimes, we like short variables. The linter doesn't catch cases when a short name is good - whitespace # no added value @@ -54,10 +83,12 @@ Here are the reasons why they are disabled (update: Nov. 2025, `golangci-lint v2 - wsl_v5 # no added value. Noise ``` -As you may see, we agree with the objectives of most linters, at least the principle they are supposed to enforce. -But all linters do not support fine-grained tuning to tolerate some cases and not some others. +As you may see, we agree with the objective of most linters, at least the principle they are supposed to enforce. +But all linters do not support fine-grained tuning to tolerate some cases and not some others. -When this is possible, we enable linters with relaxed constraints: +**Relaxed linter settings** + +When this is possible, we enable linters with relaxed constraints. ```yaml settings: @@ -81,3 +112,6 @@ When this is possible, we enable linters with relaxed constraints: Final note: since we have switched to a forked version of `stretchr/testify`, we no longer benefit from the great `testifylint` linter for tests. + +[golangci-yml]: https://github.com/go-openapi/errors/blob/master/.golangci.yml +[golangci-doc]: https://golangci-lint.run/docs/linters/configuration/ diff --git a/examples_test.go b/examples_test.go index 745c272..e719953 100644 --- a/examples_test.go +++ b/examples_test.go @@ -63,7 +63,7 @@ func ExampleServeError() { } func ExampleCompositeValidationError() { - var errs []error + errs := make([]error, 0, 3) // Collect multiple validation errors errs = append(errs, errors.Required("name", "body", nil)) diff --git a/middleware_test.go b/middleware_test.go index 17e2cd4..9d897d5 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -18,5 +18,5 @@ func TestAPIVerificationFailed(t *testing.T) { expected := `missing [text/html, application/xml] consumer registrations missing from spec file [application/json, application/x-yaml] consumer` - assert.Equal(t, expected, err.Error()) + assert.EqualT(t, expected, err.Error()) } diff --git a/parsing_test.go b/parsing_test.go index cc71616..d9db97b 100644 --- a/parsing_test.go +++ b/parsing_test.go @@ -14,9 +14,9 @@ import ( func TestParseError(t *testing.T) { err := NewParseError("Content-Type", "header", "application(", errors.New("unable to parse")) assert.EqualValues(t, 400, err.Code()) - assert.Equal(t, "parsing Content-Type header from \"application(\" failed, because unable to parse", err.Error()) + assert.EqualT(t, "parsing Content-Type header from \"application(\" failed, because unable to parse", err.Error()) err = NewParseError("Content-Type", "", "application(", errors.New("unable to parse")) assert.EqualValues(t, 400, err.Code()) - assert.Equal(t, "parsing Content-Type from \"application(\" failed, because unable to parse", err.Error()) + assert.EqualT(t, "parsing Content-Type from \"application(\" failed, because unable to parse", err.Error()) } diff --git a/schema_test.go b/schema_test.go index 8a99784..4cefa6d 100644 --- a/schema_test.go +++ b/schema_test.go @@ -18,63 +18,63 @@ func TestSchemaErrors(t *testing.T) { err := InvalidType("confirmed", "query", "boolean", nil) require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "confirmed in query must be of type boolean", err.Error()) + assert.EqualT(t, "confirmed in query must be of type boolean", err.Error()) err = InvalidType("confirmed", "", "boolean", nil) require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "confirmed must be of type boolean", err.Error()) + assert.EqualT(t, "confirmed must be of type boolean", err.Error()) err = InvalidType("confirmed", "query", "boolean", "hello") require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "confirmed in query must be of type boolean: \"hello\"", err.Error()) + assert.EqualT(t, "confirmed in query must be of type boolean: \"hello\"", err.Error()) err = InvalidType("confirmed", "query", "boolean", errors.New("hello")) require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "confirmed in query must be of type boolean, because: hello", err.Error()) + assert.EqualT(t, "confirmed in query must be of type boolean, because: hello", err.Error()) err = InvalidType("confirmed", "", "boolean", "hello") require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "confirmed must be of type boolean: \"hello\"", err.Error()) + assert.EqualT(t, "confirmed must be of type boolean: \"hello\"", err.Error()) err = InvalidType("confirmed", "", "boolean", errors.New("hello")) require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "confirmed must be of type boolean, because: hello", err.Error()) + assert.EqualT(t, "confirmed must be of type boolean, because: hello", err.Error()) }) t.Run("with DuplicateItems", func(t *testing.T) { err := DuplicateItems("uniques", "query") require.Error(t, err) assert.EqualValues(t, UniqueFailCode, err.Code()) - assert.Equal(t, "uniques in query shouldn't contain duplicates", err.Error()) + assert.EqualT(t, "uniques in query shouldn't contain duplicates", err.Error()) err = DuplicateItems("uniques", "") require.Error(t, err) assert.EqualValues(t, UniqueFailCode, err.Code()) - assert.Equal(t, "uniques shouldn't contain duplicates", err.Error()) + assert.EqualT(t, "uniques shouldn't contain duplicates", err.Error()) }) t.Run("with TooMany/TooFew Items", func(t *testing.T) { err := TooManyItems("something", "query", 5, 6) require.Error(t, err) assert.EqualValues(t, MaxItemsFailCode, err.Code()) - assert.Equal(t, "something in query should have at most 5 items", err.Error()) + assert.EqualT(t, "something in query should have at most 5 items", err.Error()) assert.Equal(t, 6, err.Value) err = TooManyItems("something", "", 5, 6) require.Error(t, err) assert.EqualValues(t, MaxItemsFailCode, err.Code()) - assert.Equal(t, "something should have at most 5 items", err.Error()) + assert.EqualT(t, "something should have at most 5 items", err.Error()) assert.Equal(t, 6, err.Value) err = TooFewItems("something", "", 5, 4) require.Error(t, err) assert.EqualValues(t, MinItemsFailCode, err.Code()) - assert.Equal(t, "something should have at least 5 items", err.Error()) + assert.EqualT(t, "something should have at least 5 items", err.Error()) assert.Equal(t, 4, err.Value) }) @@ -82,73 +82,73 @@ func TestSchemaErrors(t *testing.T) { err := ExceedsMaximumInt("something", "query", 5, false, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something in query should be less than or equal to 5", err.Error()) + assert.EqualT(t, "something in query should be less than or equal to 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumInt("something", "", 5, false, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something should be less than or equal to 5", err.Error()) + assert.EqualT(t, "something should be less than or equal to 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumInt("something", "query", 5, true, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something in query should be less than 5", err.Error()) + assert.EqualT(t, "something in query should be less than 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumInt("something", "", 5, true, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something should be less than 5", err.Error()) + assert.EqualT(t, "something should be less than 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumUint("something", "query", 5, false, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something in query should be less than or equal to 5", err.Error()) + assert.EqualT(t, "something in query should be less than or equal to 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumUint("something", "", 5, false, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something should be less than or equal to 5", err.Error()) + assert.EqualT(t, "something should be less than or equal to 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumUint("something", "query", 5, true, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something in query should be less than 5", err.Error()) + assert.EqualT(t, "something in query should be less than 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximumUint("something", "", 5, true, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something should be less than 5", err.Error()) + assert.EqualT(t, "something should be less than 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximum("something", "query", 5, false, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something in query should be less than or equal to 5", err.Error()) + assert.EqualT(t, "something in query should be less than or equal to 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximum("something", "", 5, false, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something should be less than or equal to 5", err.Error()) + assert.EqualT(t, "something should be less than or equal to 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximum("something", "query", 5, true, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something in query should be less than 5", err.Error()) + assert.EqualT(t, "something in query should be less than 5", err.Error()) assert.Equal(t, 6, err.Value) err = ExceedsMaximum("something", "", 5, true, 6) require.Error(t, err) assert.EqualValues(t, MaxFailCode, err.Code()) - assert.Equal(t, "something should be less than 5", err.Error()) + assert.EqualT(t, "something should be less than 5", err.Error()) assert.Equal(t, 6, err.Value) }) @@ -156,79 +156,79 @@ func TestSchemaErrors(t *testing.T) { err := ExceedsMinimumInt("something", "query", 5, false, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something in query should be greater than or equal to 5", err.Error()) + assert.EqualT(t, "something in query should be greater than or equal to 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumInt("something", "", 5, false, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something should be greater than or equal to 5", err.Error()) + assert.EqualT(t, "something should be greater than or equal to 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumInt("something", "query", 5, true, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something in query should be greater than 5", err.Error()) + assert.EqualT(t, "something in query should be greater than 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumInt("something", "", 5, true, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something should be greater than 5", err.Error()) + assert.EqualT(t, "something should be greater than 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumUint("something", "query", 5, false, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something in query should be greater than or equal to 5", err.Error()) + assert.EqualT(t, "something in query should be greater than or equal to 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumUint("something", "", 5, false, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something should be greater than or equal to 5", err.Error()) + assert.EqualT(t, "something should be greater than or equal to 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumUint("something", "query", 5, true, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something in query should be greater than 5", err.Error()) + assert.EqualT(t, "something in query should be greater than 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimumUint("something", "", 5, true, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something should be greater than 5", err.Error()) + assert.EqualT(t, "something should be greater than 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimum("something", "query", 5, false, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something in query should be greater than or equal to 5", err.Error()) + assert.EqualT(t, "something in query should be greater than or equal to 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimum("something", "", 5, false, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something should be greater than or equal to 5", err.Error()) + assert.EqualT(t, "something should be greater than or equal to 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimum("something", "query", 5, true, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something in query should be greater than 5", err.Error()) + assert.EqualT(t, "something in query should be greater than 5", err.Error()) assert.Equal(t, 4, err.Value) err = ExceedsMinimum("something", "", 5, true, 4) require.Error(t, err) assert.EqualValues(t, MinFailCode, err.Code()) - assert.Equal(t, "something should be greater than 5", err.Error()) + assert.EqualT(t, "something should be greater than 5", err.Error()) assert.Equal(t, 4, err.Value) err = NotMultipleOf("something", "query", 5, 1) require.Error(t, err) assert.EqualValues(t, MultipleOfFailCode, err.Code()) - assert.Equal(t, "something in query should be a multiple of 5", err.Error()) + assert.EqualT(t, "something in query should be a multiple of 5", err.Error()) assert.Equal(t, 1, err.Value) }) @@ -236,31 +236,31 @@ func TestSchemaErrors(t *testing.T) { err := NotMultipleOf("something", "query", float64(5), float64(1)) require.Error(t, err) assert.EqualValues(t, MultipleOfFailCode, err.Code()) - assert.Equal(t, "something in query should be a multiple of 5", err.Error()) + assert.EqualT(t, "something in query should be a multiple of 5", err.Error()) assert.InDelta(t, float64(1), err.Value, 1e-6) err = NotMultipleOf("something", "query", uint64(5), uint64(1)) require.Error(t, err) assert.EqualValues(t, MultipleOfFailCode, err.Code()) - assert.Equal(t, "something in query should be a multiple of 5", err.Error()) + assert.EqualT(t, "something in query should be a multiple of 5", err.Error()) assert.Equal(t, uint64(1), err.Value) err = NotMultipleOf("something", "", 5, 1) require.Error(t, err) assert.EqualValues(t, MultipleOfFailCode, err.Code()) - assert.Equal(t, "something should be a multiple of 5", err.Error()) + assert.EqualT(t, "something should be a multiple of 5", err.Error()) assert.Equal(t, 1, err.Value) err = MultipleOfMustBePositive("path", "body", float64(-10)) require.Error(t, err) assert.EqualValues(t, MultipleOfMustBePositiveCode, err.Code()) - assert.Equal(t, `factor MultipleOf declared for path must be positive: -10`, err.Error()) + assert.EqualT(t, `factor MultipleOf declared for path must be positive: -10`, err.Error()) assert.InDelta(t, float64(-10), err.Value, 1e-6) err = MultipleOfMustBePositive("path", "body", int64(-10)) require.Error(t, err) assert.EqualValues(t, MultipleOfMustBePositiveCode, err.Code()) - assert.Equal(t, `factor MultipleOf declared for path must be positive: -10`, err.Error()) + assert.EqualT(t, `factor MultipleOf declared for path must be positive: -10`, err.Error()) assert.Equal(t, int64(-10), err.Value) }) @@ -268,13 +268,13 @@ func TestSchemaErrors(t *testing.T) { err := EnumFail("something", "query", "yada", []any{"hello", "world"}) require.Error(t, err) assert.EqualValues(t, EnumFailCode, err.Code()) - assert.Equal(t, "something in query should be one of [hello world]", err.Error()) + assert.EqualT(t, "something in query should be one of [hello world]", err.Error()) assert.Equal(t, "yada", err.Value) err = EnumFail("something", "", "yada", []any{"hello", "world"}) require.Error(t, err) assert.EqualValues(t, EnumFailCode, err.Code()) - assert.Equal(t, "something should be one of [hello world]", err.Error()) + assert.EqualT(t, "something should be one of [hello world]", err.Error()) assert.Equal(t, "yada", err.Value) }) @@ -282,13 +282,13 @@ func TestSchemaErrors(t *testing.T) { err := Required("something", "query", nil) require.Error(t, err) assert.EqualValues(t, RequiredFailCode, err.Code()) - assert.Equal(t, "something in query is required", err.Error()) + assert.EqualT(t, "something in query is required", err.Error()) assert.Nil(t, err.Value) err = Required("something", "", nil) require.Error(t, err) assert.EqualValues(t, RequiredFailCode, err.Code()) - assert.Equal(t, "something is required", err.Error()) + assert.EqualT(t, "something is required", err.Error()) assert.Nil(t, err.Value) }) @@ -296,13 +296,13 @@ func TestSchemaErrors(t *testing.T) { err := ReadOnly("something", "query", nil) require.Error(t, err) assert.EqualValues(t, ReadOnlyFailCode, err.Code()) - assert.Equal(t, "something in query is readOnly", err.Error()) + assert.EqualT(t, "something in query is readOnly", err.Error()) assert.Nil(t, err.Value) err = ReadOnly("something", "", nil) require.Error(t, err) assert.EqualValues(t, ReadOnlyFailCode, err.Code()) - assert.Equal(t, "something is readOnly", err.Error()) + assert.EqualT(t, "something is readOnly", err.Error()) assert.Nil(t, err.Value) }) @@ -310,25 +310,25 @@ func TestSchemaErrors(t *testing.T) { err := TooLong("something", "query", 5, "abcdef") require.Error(t, err) assert.EqualValues(t, TooLongFailCode, err.Code()) - assert.Equal(t, "something in query should be at most 5 chars long", err.Error()) + assert.EqualT(t, "something in query should be at most 5 chars long", err.Error()) assert.Equal(t, "abcdef", err.Value) err = TooLong("something", "", 5, "abcdef") require.Error(t, err) assert.EqualValues(t, TooLongFailCode, err.Code()) - assert.Equal(t, "something should be at most 5 chars long", err.Error()) + assert.EqualT(t, "something should be at most 5 chars long", err.Error()) assert.Equal(t, "abcdef", err.Value) err = TooShort("something", "query", 5, "a") require.Error(t, err) assert.EqualValues(t, TooShortFailCode, err.Code()) - assert.Equal(t, "something in query should be at least 5 chars long", err.Error()) + assert.EqualT(t, "something in query should be at least 5 chars long", err.Error()) assert.Equal(t, "a", err.Value) err = TooShort("something", "", 5, "a") require.Error(t, err) assert.EqualValues(t, TooShortFailCode, err.Code()) - assert.Equal(t, "something should be at least 5 chars long", err.Error()) + assert.EqualT(t, "something should be at least 5 chars long", err.Error()) assert.Equal(t, "a", err.Value) }) @@ -336,13 +336,13 @@ func TestSchemaErrors(t *testing.T) { err := FailedPattern("something", "query", "\\d+", "a") require.Error(t, err) assert.EqualValues(t, PatternFailCode, err.Code()) - assert.Equal(t, "something in query should match '\\d+'", err.Error()) + assert.EqualT(t, "something in query should match '\\d+'", err.Error()) assert.Equal(t, "a", err.Value) err = FailedPattern("something", "", "\\d+", "a") require.Error(t, err) assert.EqualValues(t, PatternFailCode, err.Code()) - assert.Equal(t, "something should match '\\d+'", err.Error()) + assert.EqualT(t, "something should match '\\d+'", err.Error()) assert.Equal(t, "a", err.Value) }) @@ -350,38 +350,38 @@ func TestSchemaErrors(t *testing.T) { err := InvalidTypeName("something") require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "something is an invalid type name", err.Error()) + assert.EqualT(t, "something is an invalid type name", err.Error()) }) t.Run("with AdditionalItemsNotAllowed", func(t *testing.T) { err := AdditionalItemsNotAllowed("something", "query") require.Error(t, err) assert.EqualValues(t, NoAdditionalItemsCode, err.Code()) - assert.Equal(t, "something in query can't have additional items", err.Error()) + assert.EqualT(t, "something in query can't have additional items", err.Error()) err = AdditionalItemsNotAllowed("something", "") require.Error(t, err) assert.EqualValues(t, NoAdditionalItemsCode, err.Code()) - assert.Equal(t, "something can't have additional items", err.Error()) + assert.EqualT(t, "something can't have additional items", err.Error()) }) err := InvalidCollectionFormat("something", "query", "yada") require.Error(t, err) assert.EqualValues(t, InvalidTypeCode, err.Code()) - assert.Equal(t, "the collection format \"yada\" is not supported for the query param \"something\"", err.Error()) + assert.EqualT(t, "the collection format \"yada\" is not supported for the query param \"something\"", err.Error()) t.Run("with CompositeValidationError", func(t *testing.T) { err := CompositeValidationError() require.Error(t, err) assert.EqualValues(t, CompositeErrorCode, err.Code()) - assert.Equal(t, "validation failure list", err.Error()) + assert.EqualT(t, "validation failure list", err.Error()) testErr1 := errors.New("first error") testErr2 := errors.New("second error") err = CompositeValidationError(testErr1, testErr2) require.Error(t, err) assert.EqualValues(t, CompositeErrorCode, err.Code()) - assert.Equal(t, "validation failure list:\nfirst error\nsecond error", err.Error()) + assert.EqualT(t, "validation failure list:\nfirst error\nsecond error", err.Error()) require.ErrorIs(t, err, testErr1) require.ErrorIs(t, err, testErr2) @@ -399,7 +399,7 @@ func TestSchemaErrors(t *testing.T) { new-name.unsupported media type "text/html", only [application/json] are allowed validation failure list: new-namey is an invalid type name` - assert.Equal(t, expectedMessage, err.Error()) + assert.EqualT(t, expectedMessage, err.Error()) }) t.Run("ValidateName for a CompositeError should skip nil members", func(t *testing.T) { @@ -422,7 +422,7 @@ another error some error` require.Len(t, mutated.Errors, len(err.Errors)) - assert.Equal(t, expectedMessage, mutated.Error()) + assert.EqualT(t, expectedMessage, mutated.Error()) }) t.Run("with PropertyNotAllowed", func(t *testing.T) { @@ -430,13 +430,13 @@ some error` require.Error(t, err) assert.EqualValues(t, UnallowedPropertyCode, err.Code()) // unallowedProperty = "%s.%s in %s is a forbidden property" - assert.Equal(t, "path.key in body is a forbidden property", err.Error()) + assert.EqualT(t, "path.key in body is a forbidden property", err.Error()) err = PropertyNotAllowed("path", "", "key") require.Error(t, err) assert.EqualValues(t, UnallowedPropertyCode, err.Code()) // unallowedPropertyNoIn = "%s.%s is a forbidden property" - assert.Equal(t, "path.key is a forbidden property", err.Error()) + assert.EqualT(t, "path.key is a forbidden property", err.Error()) }) t.Run("with TooMany/TooFew properties", func(t *testing.T) { @@ -444,25 +444,25 @@ some error` require.Error(t, err) assert.EqualValues(t, TooManyPropertiesCode, err.Code()) // tooManyProperties = "%s in %s should have at most %d properties" - assert.Equal(t, "path in body should have at most 10 properties", err.Error()) + assert.EqualT(t, "path in body should have at most 10 properties", err.Error()) err = TooManyProperties("path", "", 10) require.Error(t, err) assert.EqualValues(t, TooManyPropertiesCode, err.Code()) // tooManyPropertiesNoIn = "%s should have at most %d properties" - assert.Equal(t, "path should have at most 10 properties", err.Error()) + assert.EqualT(t, "path should have at most 10 properties", err.Error()) err = TooFewProperties("path", "body", 10) require.Error(t, err) assert.EqualValues(t, TooFewPropertiesCode, err.Code()) // tooFewProperties = "%s in %s should have at least %d properties" - assert.Equal(t, "path in body should have at least 10 properties", err.Error()) + assert.EqualT(t, "path in body should have at least 10 properties", err.Error()) err = TooFewProperties("path", "", 10) require.Error(t, err) assert.EqualValues(t, TooFewPropertiesCode, err.Code()) // tooFewPropertiesNoIn = "%s should have at least %d properties" - assert.Equal(t, "path should have at least 10 properties", err.Error()) + assert.EqualT(t, "path should have at least 10 properties", err.Error()) }) t.Run("with PatternProperties", func(t *testing.T) { @@ -470,12 +470,12 @@ some error` require.Error(t, err) assert.EqualValues(t, FailedAllPatternPropsCode, err.Code()) // failedAllPatternProps = "%s.%s in %s failed all pattern properties" - assert.Equal(t, "path.key in body failed all pattern properties", err.Error()) + assert.EqualT(t, "path.key in body failed all pattern properties", err.Error()) err = FailedAllPatternProperties("path", "", "key") require.Error(t, err) assert.EqualValues(t, FailedAllPatternPropsCode, err.Code()) // failedAllPatternPropsNoIn = "%s.%s failed all pattern properties" - assert.Equal(t, "path.key failed all pattern properties", err.Error()) + assert.EqualT(t, "path.key failed all pattern properties", err.Error()) }) }